Mercurial > pidgin.yaz
view libpurple/protocols/mxit/protocol.c @ 30078:51f997e2347f
Some improvements to the new CS-via-HTTP stuff, sparked by Stu's suggestion to
iterate over the entire respose returned from the HTTP GET.
author | John Bailey <rekkanoryo@rekkanoryo.org> |
---|---|
date | Sat, 03 Apr 2010 05:09:28 +0000 |
parents | 81ea740f92a4 |
children | 1147389b5424 |
line wrap: on
line source
/* * MXit Protocol libPurple Plugin * * -- MXit client protocol implementation -- * * Pieter Loubser <libpurple@mxit.com> * * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd. * <http://www.mxitlifestyle.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "purple.h" #include "protocol.h" #include "mxit.h" #include "roster.h" #include "chunk.h" #include "filexfer.h" #include "markup.h" #include "multimx.h" #include "splashscreen.h" #include "login.h" #include "formcmds.h" #include "http.h" #define MXIT_MS_OFFSET 3 /* configure the right record terminator char to use */ #define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM ) /*------------------------------------------------------------------------ * Display a notification popup message to the user. * * @param type The type of notification: * - info: PURPLE_NOTIFY_MSG_INFO * - warning: PURPLE_NOTIFY_MSG_WARNING * - error: PURPLE_NOTIFY_MSG_ERROR * @param heading Heading text * @param message Message text */ void mxit_popup( int type, const char* heading, const char* message ) { /* (reference: "libpurple/notify.h") */ purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL ); } /*------------------------------------------------------------------------ * For compatibility with legacy clients, all usernames are sent from MXit with a domain * appended. For MXit contacts, this domain is set to "@m". This function strips * those fake domains. * * @param username The username of the contact */ void mxit_strip_domain( char* username ) { if ( g_str_has_suffix( username, "@m" ) ) username[ strlen(username) - 2 ] = '\0'; } /*------------------------------------------------------------------------ * Dump a byte buffer to the console for debugging purposes. * * @param buf The data * @param len The data length */ void dump_bytes( struct MXitSession* session, const char* buf, int len ) { char msg[( len * 3 ) + 1]; int i; memset( msg, 0x00, sizeof( msg ) ); for ( i = 0; i < len; i++ ) { if ( buf[i] == CP_REC_TERM ) /* record terminator */ msg[i] = '!'; else if ( buf[i] == CP_FLD_TERM ) /* field terminator */ msg[i] = '^'; else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */ msg[i] = '@'; else if ( buf[i] < 0x20 ) msg[i] = '_'; else msg[i] = buf[i]; } purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg ); } /*------------------------------------------------------------------------ * Determine if we have an active chat with a specific contact * * @param session The MXit session object * @param who The contact name * @return Return true if we have an active chat with the contact */ gboolean find_active_chat( const GList* chats, const char* who ) { const GList* list = chats; const char* chat = NULL; while ( list ) { chat = (const char*) list->data; if ( strcmp( chat, who ) == 0 ) return TRUE; list = g_list_next( list ); } return FALSE; } /*======================================================================================================================== * Low-level Packet transmission */ /*------------------------------------------------------------------------ * Remove next packet from transmission queue. * * @param session The MXit session object * @return The next packet for transmission (or NULL) */ static struct tx_packet* pop_tx_packet( struct MXitSession* session ) { struct tx_packet* packet = NULL; if ( session->queue.count > 0 ) { /* dequeue the next packet */ packet = session->queue.packets[session->queue.rd_i]; session->queue.packets[session->queue.rd_i] = NULL; session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE; session->queue.count--; } return packet; } /*------------------------------------------------------------------------ * Add packet to transmission queue. * * @param session The MXit session object * @param packet The packet to transmit * @return Return TRUE if packet was enqueue, or FALSE if queue is full. */ static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet ) { if ( session->queue.count < MAX_QUEUE_SIZE ) { /* enqueue packet */ session->queue.packets[session->queue.wr_i] = packet; session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE; session->queue.count++; return TRUE; } else return FALSE; /* queue is full */ } /*------------------------------------------------------------------------ * Deallocate transmission packet. * * @param packet The packet to deallocate. */ static void free_tx_packet( struct tx_packet* packet ) { g_free( packet->data ); g_free( packet ); packet = NULL; } /*------------------------------------------------------------------------ * Flush all the packets from the tx queue and release the resources. * * @param session The MXit session object */ static void flush_queue( struct MXitSession* session ) { struct tx_packet* packet; purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" ); while ( (packet = pop_tx_packet( session ) ) != NULL ) free_tx_packet( packet ); } /*------------------------------------------------------------------------ * TX Step 3: Write the packet data to the TCP connection. * * @param fd The file descriptor * @param pktdata The packet data * @param pktlen The length of the packet data * @return Return -1 on error, otherwise 0 */ static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen ) { int written; int res; written = 0; while ( written < pktlen ) { res = write( fd, &pktdata[written], pktlen - written ); if ( res <= 0 ) { /* error on socket */ if ( errno == EAGAIN ) continue; purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res ); return -1; } written += res; } return 0; } /*------------------------------------------------------------------------ * Callback called for handling a HTTP GET response * * @param url_data libPurple internal object (see purple_util_fetch_url_request) * @param user_data The MXit session object * @param url_text The data returned (could be NULL if error) * @param len The length of the data returned (0 if error) * @param error_message Descriptive error message */ static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message ) { struct MXitSession* session = (struct MXitSession*) user_data; /* clear outstanding request */ session->http_out_req = NULL; if ( ( !url_text ) || ( len == 0 ) ) { /* error with request */ purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message ); return; } /* convert the HTTP result */ memcpy( session->rx_dbuf, url_text, len ); session->rx_i = len; mxit_parse_packet( session ); } /*------------------------------------------------------------------------ * TX Step 3: Write the packet data to the HTTP connection (GET style). * * @param session The MXit session object * @param pktdata The packet data * @param pktlen The length of the packet data * @return Return -1 on error, otherwise 0 */ static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet ) { char* part = NULL; char* url = NULL; if ( packet->datalen > 0 ) { char* tmp = NULL; tmp = g_strndup( packet->data, packet->datalen ); part = g_strdup( purple_url_encode( tmp ) ); g_free( tmp ); } url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part ); #ifdef DEBUG_PROTOCOL purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url ); #endif /* send the HTTP request */ session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session ); g_free( url ); if ( part ) g_free( part ); } /*------------------------------------------------------------------------ * TX Step 3: Write the packet data to the HTTP connection (POST style). * * @param session The MXit session object * @param pktdata The packet data * @param pktlen The length of the packet data * @return Return -1 on error, otherwise 0 */ static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet ) { char request[256 + packet->datalen]; int reqlen; char* host_name; int host_port; gboolean ok; /* extract the HTTP host name and host port number to connect to */ ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL ); if ( !ok ) { purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server ); } /* strip off the last '&' from the header */ packet->header[packet->headerlen - 1] = '\0'; packet->headerlen--; /* build the HTTP request packet */ reqlen = g_snprintf( request, 256, "POST %s?%s HTTP/1.1\r\n" "User-Agent: " MXIT_HTTP_USERAGENT "\r\n" "Content-Type: application/octet-stream\r\n" "Host: %s\r\n" "Content-Length: %d\r\n" "\r\n", session->http_server, purple_url_encode( packet->header ), host_name, packet->datalen - MXIT_MS_OFFSET ); /* copy over the packet body data (could be binary) */ memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET ); reqlen += packet->datalen; #ifdef DEBUG_PROTOCOL purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" ); dump_bytes( session, request, reqlen ); #endif /* send the request to the HTTP server */ mxit_http_send_request( session, host_name, host_port, request, reqlen ); } /*------------------------------------------------------------------------ * TX Step 2: Handle the transmission of the packet to the MXit server. * * @param session The MXit session object * @param packet The packet to transmit */ static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet ) { int res; if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { /* we are not connected so ignore all packets to be send */ purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" ); return; } purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen ); #ifdef DEBUG_PROTOCOL dump_bytes( session, packet->header, packet->headerlen ); dump_bytes( session, packet->data, packet->datalen ); #endif if ( !session->http ) { /* socket connection */ char data[packet->datalen + packet->headerlen]; int datalen; /* create raw data buffer */ memcpy( data, packet->header, packet->headerlen ); memcpy( data + packet->headerlen, packet->data, packet->datalen ); datalen = packet->headerlen + packet->datalen; res = mxit_write_sock_packet( session->fd, data, datalen ); if ( res < 0 ) { /* we must have lost the connection, so terminate it so that we can reconnect */ purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) ); } } else { /* http connection */ if ( packet->cmd == CP_CMD_MEDIA ) { /* multimedia packets must be send with a HTTP POST */ mxit_write_http_post( session, packet ); } else { mxit_write_http_get( session, packet ); } } /* update the timestamp of the last-transmitted packet */ session->last_tx = time( NULL ); /* * we need to remember that we are still waiting for the ACK from * the server on this request */ session->outack = packet->cmd; /* free up the packet resources */ free_tx_packet( packet ); } /*------------------------------------------------------------------------ * TX Step 1: Create a new Tx packet and queue it for sending. * * @param session The MXit session object * @param data The packet data (payload) * @param datalen The length of the packet data * @param cmd The MXit command for this packet */ static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd ) { struct tx_packet* packet; char header[256]; int hlen; /* create a packet for sending */ packet = g_new0( struct tx_packet, 1 ); packet->data = g_malloc0( datalen ); packet->cmd = cmd; packet->headerlen = 0; /* create generic packet header */ hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */ if ( session->http ) { /* http connection only */ hlen += sprintf( header + hlen, "s=" ); if ( session->http_sesid > 0 ) { hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */ } session->http_seqno++; hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */ } hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */ if ( !session->http ) { /* socket connection only */ packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */ } /* copy the header to packet */ memcpy( packet->header + packet->headerlen, header, hlen ); packet->headerlen += hlen; /* copy payload to packet */ if ( datalen > 0 ) memcpy( packet->data, data, datalen ); packet->datalen = datalen; /* * shortcut: first check if there are any commands still outstanding. * if not, then we might as well just write this packet directly and * skip the whole queueing thing */ if ( session->outack == 0 ) { /* no outstanding ACKs, so we might as well write it directly */ mxit_send_packet( session, packet ); } else { /* ACK still outstanding, so we need to queue this request until we have the ACK */ if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) { /* we do NOT queue HTTP poll nor socket ping packets */ free_tx_packet( packet ); return; } purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd ); if ( !push_tx_packet( session, packet ) ) { /* packet could not be queued for transmission */ mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) ); free_tx_packet( packet ); } } } /*------------------------------------------------------------------------ * Callback to manage the packet send queue (send next packet, timeout's, etc). * * @param session The MXit session object */ gboolean mxit_manage_queue( gpointer user_data ) { struct MXitSession* session = (struct MXitSession*) user_data; struct tx_packet* packet = NULL; if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { /* we are not connected, so ignore the queue */ return TRUE; } else if ( session->outack > 0 ) { /* we are still waiting for an outstanding ACK from the MXit server */ if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) { /* ack timeout! so we close the connection here */ purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack ); purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) ); } return TRUE; } packet = pop_tx_packet( session ); if ( packet != NULL ) { /* there was a packet waiting to be sent to the server, now is the time to do something about it */ /* send the packet to MXit server */ mxit_send_packet( session, packet ); } return TRUE; } /*------------------------------------------------------------------------ * Callback to manage HTTP server polling (HTTP connections ONLY) * * @param session The MXit session object */ gboolean mxit_manage_polling( gpointer user_data ) { struct MXitSession* session = (struct MXitSession*) user_data; gboolean poll = FALSE; time_t now = time( NULL ); int polldiff; int rxdiff; if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { /* we only poll if we are actually logged in */ return TRUE; } /* calculate the time differences */ rxdiff = now - session->last_rx; polldiff = now - session->http_last_poll; if ( rxdiff < MXIT_HTTP_POLL_MIN ) { /* we received some reply a few moments ago, so reset the poll interval */ session->http_interval = MXIT_HTTP_POLL_MIN; } else if ( session->http_last_poll < ( now - session->http_interval ) ) { /* time to poll again */ poll = TRUE; /* back-off some more with the polling */ session->http_interval = session->http_interval + ( session->http_interval / 2 ); if ( session->http_interval > MXIT_HTTP_POLL_MAX ) session->http_interval = MXIT_HTTP_POLL_MAX; } /* debugging */ //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff ); if ( poll ) { /* send poll request */ session->http_last_poll = time( NULL ); mxit_send_poll( session ); } return TRUE; } /*======================================================================================================================== * Send MXit operations. */ /*------------------------------------------------------------------------ * Send a ping/keepalive packet to MXit server. * * @param session The MXit session object */ void mxit_send_ping( struct MXitSession* session ) { /* queue packet for transmission */ mxit_queue_packet( session, NULL, 0, CP_CMD_PING ); } /*------------------------------------------------------------------------ * Send a poll request to the HTTP server (HTTP connections ONLY). * * @param session The MXit session object */ void mxit_send_poll( struct MXitSession* session ) { /* queue packet for transmission */ mxit_queue_packet( session, NULL, 0, CP_CMD_POLL ); } /*------------------------------------------------------------------------ * Send a logout packet to the MXit server. * * @param session The MXit session object */ void mxit_send_logout( struct MXitSession* session ) { /* queue packet for transmission */ mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT ); } /*------------------------------------------------------------------------ * Send a register packet to the MXit server. * * @param session The MXit session object */ void mxit_send_register( struct MXitSession* session ) { struct MXitProfile* profile = session->profile; const char* locale; char data[CP_MAX_PACKET]; int datalen; locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */ "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */ "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM, profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER ); } /*------------------------------------------------------------------------ * Send a login packet to the MXit server. * * @param session The MXit session object */ void mxit_send_login( struct MXitSession* session ) { const char* splashId; const char* locale; char data[CP_MAX_PACKET]; int datalen; locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */ "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */ "%s%c%s", /* dialingcode\1locale */ session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale ); /* include "custom resource" information */ splashId = splash_current( session ); if ( splashId != NULL ) datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN ); } /*------------------------------------------------------------------------ * Send a chat message packet to the MXit server. * * @param session The MXit session object * @param to The username of the recipient * @param msg The message text */ void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup, gboolean is_command ) { char data[CP_MAX_PACKET]; char* markuped_msg; int datalen; int msgtype = ( is_command ? CP_MSGTYPE_COMMAND : CP_MSGTYPE_NORMAL ); /* first we need to convert the markup from libPurple to MXit format */ if ( parse_markup ) markuped_msg = mxit_convert_markup_tx( msg, &msgtype ); else markuped_msg = g_strdup( msg ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */ to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON ); /* free the resources */ g_free( markuped_msg ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG ); } /*------------------------------------------------------------------------ * Send a extended profile request packet to the MXit server. * * @param session The MXit session object * @param username Username who's profile is being requested (NULL = our own) * @param nr_attribs Number of attributes being requested * @param attributes The names of the attributes */ void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] ) { char data[CP_MAX_PACKET]; int datalen; unsigned int i; datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */ (username ? username : ""), CP_FLD_TERM, nr_attrib); /* add attributes */ for ( i = 0; i < nr_attrib; i++ ) datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET ); } /*------------------------------------------------------------------------ * Send an update profile packet to the MXit server. * * @param session The MXit session object * @param password The new password to be used for logging in (optional) * @param nr_attrib The number of attributes * @param attributes String containing the attributes and settings seperated by '0x01' */ void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes ) { char data[CP_MAX_PACKET]; gchar** parts; int datalen; unsigned int i; parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */ ( password ) ? password : "", CP_FLD_TERM, nr_attrib ); /* add attributes */ for ( i = 1; i < nr_attrib * 3; i+=3 ) datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */ CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET ); /* freeup the memory */ g_strfreev( parts ); } /*------------------------------------------------------------------------ * Send a presence update packet to the MXit server. * * @param session The MXit session object * @param presence The presence (as per MXit types) * @param statusmsg The status message (can be NULL) */ void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */ presence, CP_FLD_TERM ); /* append status message (if one is set) */ if ( statusmsg ) datalen += sprintf( data + datalen, "%s", statusmsg ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_STATUS ); } /*------------------------------------------------------------------------ * Send a mood update packet to the MXit server. * * @param session The MXit session object * @param mood The mood (as per MXit types) */ void mxit_send_mood( struct MXitSession* session, int mood ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%i", /* "ms"=mood */ mood ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_MOOD ); } /*------------------------------------------------------------------------ * Send an invite contact packet to the MXit server. * * @param session The MXit session object * @param username The username of the contact being invited * @param alias Our alias for the contact * @param groupname Group in which contact should be stored. */ void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias, CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, "" ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_INVITE ); } /*------------------------------------------------------------------------ * Send a remove contact packet to the MXit server. * * @param session The MXit session object * @param username The username of the contact being removed */ void mxit_send_remove( struct MXitSession* session, const char* username ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s", /* "ms"=username */ username ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE ); } /*------------------------------------------------------------------------ * Send an accept subscription (invite) packet to the MXit server. * * @param session The MXit session object * @param username The username of the contact being accepted * @param alias Our alias for the contact */ void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */ username, CP_FLD_TERM, "", CP_FLD_TERM, alias ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW ); } /*------------------------------------------------------------------------ * Send an deny subscription (invite) packet to the MXit server. * * @param session The MXit session object * @param username The username of the contact being denied */ void mxit_send_deny_sub( struct MXitSession* session, const char* username ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s", /* "ms"=username */ username ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_DENY ); } /*------------------------------------------------------------------------ * Send an update contact packet to the MXit server. * * @param session The MXit session object * @param username The username of the contact being denied * @param alias Our alias for the contact * @param groupname Group in which contact should be stored. */ void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */ groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE ); } /*------------------------------------------------------------------------ * Send a splash-screen click event packet. * * @param session The MXit session object * @param splashid The identifier of the splash-screen */ void mxit_send_splashclick( struct MXitSession* session, const char* splashid ) { char data[CP_MAX_PACKET]; int datalen; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s", /* "ms"=splashId */ splashid ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK ); } /*------------------------------------------------------------------------ * Send packet to create a MultiMX room. * * @param session The MXit session object * @param groupname Name of the room to create * @param nr_usernames Number of users in initial invite * @param usernames The usernames of the users in the initial invite */ void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] ) { char data[CP_MAX_PACKET]; int datalen; int i; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */ groupname, CP_FLD_TERM, nr_usernames ); /* add usernames */ for ( i = 0; i < nr_usernames; i++ ) datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE ); } /*------------------------------------------------------------------------ * Send packet to invite users to existing MultiMX room. * * @param session The MXit session object * @param roomid The unique RoomID for the MultiMx room. * @param nr_usernames Number of users being invited * @param usernames The usernames of the users being invited */ void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] ) { char data[CP_MAX_PACKET]; int datalen; int i; /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */ roomid, CP_FLD_TERM, nr_usernames ); /* add usernames */ for ( i = 0; i < nr_usernames; i++ ) datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] ); /* queue packet for transmission */ mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE ); } /*------------------------------------------------------------------------ * Send a "send file direct" multimedia packet. * * @param session The MXit session object * @param username The username of the recipient * @param filename The name of the file being sent * @param buf The content of the file * @param buflen The length of the file contents */ void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_senddirect( chunk_data( chunk ), username, filename, buf, buflen ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_DIRECT_SND ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Send a "reject file" multimedia packet. * * @param session The MXit session object * @param fileid A unique ID that identifies this file */ void mxit_send_file_reject( struct MXitSession* session, const char* fileid ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_reject( chunk_data( chunk ), fileid ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_REJECT ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Send a "get file" multimedia packet. * * @param session The MXit session object * @param fileid A unique ID that identifies this file * @param filesize The number of bytes to retrieve * @param offset Offset in file at which to start retrieving */ void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_get( chunk_data(chunk), fileid, filesize, offset ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_GET ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Send a "received file" multimedia packet. * * @param session The MXit session object * @param status The status of the file-transfer */ void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_received( chunk_data(chunk), fileid, status ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_RECIEVED ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Send a "set avatar" multimedia packet. * * @param session The MXit session object * @param data The avatar data * @param buflen The length of the avatar data */ void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_set_avatar( chunk_data(chunk), avatar, avatarlen ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_SET_AVATAR ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Send a "get avatar" multimedia packet. * * @param session The MXit session object * @param mxitId The username who's avatar to request * @param avatarId The id of the avatar image (as string) * @param data The avatar data * @param buflen The length of the avatar data */ void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId ) { char data[CP_MAX_PACKET]; int datalen = 0; gchar* chunk; int size; purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId ); /* convert the packet to a byte stream */ datalen = sprintf( data, "ms=" ); /* map chunk header over data buffer */ chunk = &data[datalen]; size = mxit_chunk_create_get_avatar( chunk_data(chunk), mxitId, avatarId, MXIT_AVATAR_SIZE ); if ( size < 0 ) { purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size ); return; } set_chunk_type( chunk, CP_CHUNK_GET_AVATAR ); set_chunk_length( chunk, size ); datalen += MXIT_CHUNK_HEADER_SIZE + size; /* send the byte stream to the mxit server */ mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA ); } /*------------------------------------------------------------------------ * Process a login message packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount ) { PurpleStatus* status; int presence; const char* statusmsg; const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME, CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL, CP_PROFILE_MOBILENR }; purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN ); /* we were not yet logged in so we need to complete the login sequence here */ session->flags |= MXIT_FLAG_LOGGEDIN; purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 ); purple_connection_set_state( session->con, PURPLE_CONNECTED ); /* display the current splash-screen */ if ( splash_popup_enabled( session ) ) splash_display( session ); /* update presence status */ status = purple_account_get_active_status( session->acc ); presence = mxit_convert_presence( purple_status_get_id( status ) ); statusmsg = purple_status_get_attr_string( status, "message" ); if ( ( presence != MXIT_PRESENCE_ONLINE ) || ( statusmsg ) ) { /* when logging into MXit, your default presence is online. but with the UI, one can change * the presence to whatever. in the case where its changed to a different presence setting * we need to send an update to the server, otherwise the user's presence will be out of * sync between the UI and MXit. */ char* statusmsg1 = purple_markup_strip_html( statusmsg ); char* statusmsg2 = g_strndup( statusmsg1, CP_MAX_STATUS_MSG ); mxit_send_presence( session, presence, statusmsg2 ); g_free( statusmsg1 ); g_free( statusmsg2 ); } /* save extra info if this is a HTTP connection */ if ( session->http ) { /* save the http server to use for this session */ g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) ); /* save the session id */ session->http_sesid = atoi( records[0]->fields[0]->data ); } /* retrieve our MXit profile */ mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist ); } /*------------------------------------------------------------------------ * Process a received message packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount ) { struct RXMsgData* mx = NULL; char* message = NULL; int msglen = 0; int msgflags = 0; int msgtype = 0; if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) { /* packet contains no message or an empty message */ return; } message = records[1]->fields[0]->data; msglen = strlen( message ); /* strip off dummy domain */ mxit_strip_domain( records[0]->fields[0]->data ); #ifdef DEBUG_PROTOCOL purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data ); #endif /* decode message flags (if any) */ if ( records[0]->fcount >= 5 ) msgflags = atoi( records[0]->fields[4]->data ); msgtype = atoi( records[0]->fields[2]->data ); if ( msgflags & CP_MSG_ENCRYPTED ) { /* this is an encrypted message. we do not currently support those so ignore it */ PurpleBuddy* buddy; const char* name; char msg[128]; buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data ); if ( buddy ) name = purple_buddy_get_alias( buddy ); else name = records[0]->fields[0]->data; g_snprintf( msg, sizeof( msg ), _( "%s sent you an encrypted message, but it is not supported on this client." ), name ); mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), msg ); return; } /* create and initialise new markup struct */ mx = g_new0( struct RXMsgData, 1 ); mx->msg = g_string_sized_new( msglen ); mx->session = session; mx->from = g_strdup( records[0]->fields[0]->data ); mx->timestamp = atoi( records[0]->fields[1]->data ); mx->got_img = FALSE; mx->chatid = -1; mx->img_count = 0; /* update list of active chats */ if ( !find_active_chat( session->active_chats, mx->from ) ) { session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) ); } if ( is_multimx_contact( session, mx->from ) ) { /* this is a MultiMx chatroom message */ multimx_message_received( mx, message, msglen, msgtype, msgflags ); } else { mxit_parse_markup( mx, message, msglen, msgtype, msgflags ); } /* we are now done parsing the message */ mx->converted = TRUE; if ( mx->img_count == 0 ) { /* we have all the data we need for this message to be displayed now. */ mxit_show_message( mx ); } else { /* this means there are still images outstanding for this message and * still need to wait for them before we can display the message. * so the image received callback function will eventually display * the message. */ } } /*------------------------------------------------------------------------ * Process a received subscription request packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount ) { struct contact* contact; struct record* rec; int i; purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount ); for ( i = 0; i < rcount; i++ ) { rec = records[i]; if ( rec->fcount < 4 ) { purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount ); break; } /* build up a new contact info struct */ contact = g_new0( struct contact, 1 ); strcpy( contact->username, rec->fields[0]->data ); mxit_strip_domain( contact->username ); /* remove dummy domain */ strcpy( contact->alias, rec->fields[1]->data ); contact->type = atoi( rec->fields[2]->data ); if ( rec->fcount >= 5 ) { /* there is a personal invite message attached */ contact->msg = strdup( rec->fields[4]->data ); } else contact->msg = NULL; /* handle the subscription */ if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */ char* creator = NULL; if ( rec->fcount >= 6 ) creator = rec->fields[5]->data; multimx_invite( session, contact, creator ); } else mxit_new_subscription( session, contact ); } } /*------------------------------------------------------------------------ * Process a received contact update packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount ) { struct contact* contact = NULL; struct record* rec; int i; purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount ); for ( i = 0; i < rcount; i++ ) { rec = records[i]; if ( rec->fcount < 6 ) { purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount ); break; } /* build up a new contact info struct */ contact = g_new0( struct contact, 1 ); strcpy( contact->groupname, rec->fields[0]->data ); strcpy( contact->username, rec->fields[1]->data ); mxit_strip_domain( contact->username ); /* remove dummy domain */ strcpy( contact->alias, rec->fields[2]->data ); contact->presence = atoi( rec->fields[3]->data ); contact->type = atoi( rec->fields[4]->data ); contact->mood = atoi( rec->fields[5]->data ); if ( rec->fcount > 6 ) { /* added in protocol 5.9.0 - flags & subtype */ contact->flags = atoi( rec->fields[6]->data ); contact->subtype = rec->fields[7]->data[0]; } /* add the contact to the buddy list */ if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */ multimx_created( session, contact ); else mxit_update_contact( session, contact ); } if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) { session->flags |= MXIT_FLAG_FIRSTROSTER; mxit_update_blist( session ); } } /*------------------------------------------------------------------------ * Process a received presence update packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount ) { struct record* rec; int i; purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount ); for ( i = 0; i < rcount; i++ ) { rec = records[i]; if ( rec->fcount < 6 ) { purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount ); break; } /* * The format of the record is: * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN */ mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */ mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ), rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data ); } } /*------------------------------------------------------------------------ * Process a received extended profile packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount ) { const char* mxitId = records[0]->fields[0]->data; struct MXitProfile* profile = NULL; int count; int i; purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId ); profile = g_new0( struct MXitProfile, 1 ); /* set the count for attributes */ count = atoi( records[0]->fields[1]->data ); for ( i = 0; i < count; i++ ) { char* fname; char* fvalue; char* fstatus; int f = ( i * 3 ) + 2; fname = records[0]->fields[f]->data; /* field name */ fvalue = records[0]->fields[f + 1]->data; /* field value */ fstatus = records[0]->fields[f + 2]->data; /* field status */ /* first check the status on the returned attribute */ if ( fstatus[0] != '0' ) { /* error: attribute requested was NOT found */ purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname ); continue; } if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) { /* birthdate */ if ( records[0]->fields[f + 1]->len > 10 ) { fvalue[10] = '\0'; records[0]->fields[f + 1]->len = 10; } memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len ); } else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) { /* gender */ profile->male = ( fvalue[0] == '1' ); } else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) { /* hide number */ profile->hidden = ( fvalue[0] == '1' ); } else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) { /* nickname */ g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) ); } else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) { /* avatar id, we just ingore it cause we dont need it */ } else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) { /* title */ g_strlcpy( profile->title, fvalue, sizeof( profile->title ) ); } else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) { /* first name */ g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) ); } else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) { /* last name */ g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) ); } else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) { /* email address */ g_strlcpy( profile->email, fvalue, sizeof( profile->email ) ); } else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) { /* mobile number */ g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) ); } else { /* invalid profile attribute */ purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname ); } } if ( records[0]->fields[0]->len == 0 ) { /* no MXit id provided, so this must be our own profile information */ if ( session->profile ) g_free( session->profile ); session->profile = profile; } else { /* display other user's profile */ mxit_show_profile( session, mxitId, profile ); /* cleanup */ g_free( profile ); } } /*------------------------------------------------------------------------ * Return the length of a multimedia chunk * * @return The actual chunk data length in bytes */ static int get_chunk_len( const char* chunkdata ) { int* sizeptr; sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */ return ntohl( *sizeptr ); } /*------------------------------------------------------------------------ * Process a received multimedia packet. * * @param session The MXit session object * @param records The packet's data records * @param rcount The number of data records */ static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount ) { char type; int size; type = records[0]->fields[0]->data[0]; size = get_chunk_len( records[0]->fields[0]->data ); purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size ); /* supported chunked data types */ switch ( type ) { case CP_CHUNK_CUSTOM : /* custom resource */ { struct cr_chunk chunk; /* decode the chunked data */ memset( &chunk, 0, sizeof( struct cr_chunk ) ); mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation ); /* this is a splash-screen operation */ if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) { if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */ struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable if ( splash != NULL ) splash_update( session, chunk.id, splash->data, splash->datalen, clickable ); } else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */ splash_remove( session ); } /* cleanup custom resources */ g_list_foreach( chunk.resources, (GFunc)g_free, NULL ); } break; case CP_CHUNK_OFFER : /* file offer */ { struct offerfile_chunk chunk; /* decode the chunked data */ memset( &chunk, 0, sizeof( struct offerfile_chunk ) ); mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); /* process the offer */ mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid ); } break; case CP_CHUNK_GET : /* get file response */ { struct getfile_chunk chunk; /* decode the chunked data */ memset( &chunk, 0, sizeof( struct getfile_chunk ) ); mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); /* process the getfile */ mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length ); } break; case CP_CHUNK_GET_AVATAR : /* get avatars */ { struct getavatar_chunk chunk; /* decode the chunked data */ memset( &chunk, 0, sizeof ( struct getavatar_chunk ) ); mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk ); /* update avatar image */ if ( chunk.data ) { purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid ); purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid ); } } break; case CP_CHUNK_SET_AVATAR : /* this is a reply packet to a set avatar request. no action is required */ break; case CP_CHUNK_DIRECT_SND : /* this is a ack for a file send. no action is required */ break; case CP_CHUNK_RECIEVED : /* this is a ack for a file received. no action is required */ break; default : purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type ); break; } } /*------------------------------------------------------------------------ * Handle a redirect sent from the MXit server. * * @param session The MXit session object * @param url The redirect information */ static void mxit_perform_redirect( struct MXitSession* session, const char* url ) { gchar** parts; gchar** host; int type; purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url ); /* tokenize the URL string */ parts = g_strsplit( url, ";", 0 ); /* Part 1: protocol://host:port */ host = g_strsplit( parts[0], ":", 4 ); if ( strcmp( host[0], "socket" ) == 0 ) { /* redirect to a MXit socket proxy */ g_strlcpy( session->server, &host[1][2], sizeof( session->server ) ); session->port = atoi( host[2] ); } else { purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) ); goto redirect_fail; } /* Part 2: type of redirect */ type = atoi( parts[1] ); if ( type == CP_REDIRECT_PERMANENT ) { /* permanent redirect, so save new MXit server and port */ purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server ); purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port ); } /* Part 3: message (optional) */ if ( parts[2] != NULL ) purple_connection_notice( session->con, parts[2] ); purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n", ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port ); /* perform the re-connect to the new MXit server */ mxit_reconnect( session ); redirect_fail: g_strfreev( parts ); g_strfreev( host ); } /*------------------------------------------------------------------------ * Process a success response received from the MXit server. * * @param session The MXit session object * @param packet The received packet */ static int process_success_response( struct MXitSession* session, struct rx_packet* packet ) { /* ignore ping/poll packets */ if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) ) session->last_rx = time( NULL ); /* * when we pass the packet records to the next level for parsing * we minus 3 records because 1) the first record is the packet * type 2) packet reply status 3) the last record is bogus */ /* packet command */ switch ( packet->cmd ) { case CP_CMD_REGISTER : /* fall through, when registeration successful, MXit will auto login */ case CP_CMD_LOGIN : /* login response */ if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) { mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 ); } break; case CP_CMD_LOGOUT : /* logout response */ session->flags &= ~MXIT_FLAG_LOGGEDIN; purple_account_disconnect( session->acc ); /* note: * we do not prompt the user here for a reconnect, because this could be the user * logging in with his phone. so we just disconnect the account otherwise * mxit will start to bounce between the phone and pidgin. also could be a valid * disconnect selected by the user. */ return -1; case CP_CMD_CONTACT : /* contact update */ mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 ); break; case CP_CMD_PRESENCE : /* presence update */ mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 ); break; case CP_CMD_RX_MSG : /* incoming message (no bogus record) */ mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 ); break; case CP_CMD_NEW_SUB : /* new subscription request */ mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 ); break; case CP_CMD_MEDIA : /* multi-media message */ mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 ); break; case CP_CMD_EXTPROFILE_GET : /* profile update */ mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 ); break; case CP_CMD_MOOD : /* mood update */ case CP_CMD_UPDATE : /* update contact information */ case CP_CMD_ALLOW : /* allow subscription ack */ case CP_CMD_DENY : /* deny subscription ack */ case CP_CMD_INVITE : /* invite contact ack */ case CP_CMD_REMOVE : /* remove contact ack */ case CP_CMD_TX_MSG : /* outgoing message ack */ case CP_CMD_STATUS : /* presence update ack */ case CP_CMD_GRPCHAT_CREATE : /* create groupchat */ case CP_CMD_GRPCHAT_INVITE : /* groupchat invite */ case CP_CMD_PING : /* ping reply */ case CP_CMD_POLL : /* HTTP poll reply */ case CP_CMD_EXTPROFILE_SET : /* profile update */ case CP_CMD_SPLASHCLICK : /* splash-screen clickthrough */ break; default : /* unknown packet */ purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd ); } return 0; } /*------------------------------------------------------------------------ * Process an error response received from the MXit server. * * @param session The MXit session object * @param packet The received packet */ static int process_error_response( struct MXitSession* session, struct rx_packet* packet ) { char errmsg[256]; const char* errdesc; /* set the error description to be shown to the user */ if ( packet->errmsg ) errdesc = packet->errmsg; else errdesc = _( "An internal MXit server error occurred." ); purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc ); if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) { /* we are not currently logged in, so we need to reconnect */ purple_connection_error( session->con, _( errdesc ) ); } /* packet command */ switch ( packet->cmd ) { case CP_CMD_REGISTER : case CP_CMD_LOGIN : if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) { mxit_perform_redirect( session, packet->errmsg ); return 0; } else { sprintf( errmsg, _( "Login error: %s (%i)" ), errdesc, packet->errcode ); purple_connection_error( session->con, errmsg ); return -1; } case CP_CMD_LOGOUT : sprintf( errmsg, _( "Logout error: %s (%i)" ), errdesc, packet->errcode ); purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) ); return -1; case CP_CMD_CONTACT : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) ); break; case CP_CMD_RX_MSG : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) ); break; case CP_CMD_TX_MSG : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) ); break; case CP_CMD_STATUS : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) ); break; case CP_CMD_MOOD : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) ); break; case CP_CMD_KICK : /* * the MXit server sends this packet if we were idle for too long. * to stop the server from closing this connection we need to resend * the login packet. */ mxit_send_login( session ); break; case CP_CMD_INVITE : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) ); break; case CP_CMD_REMOVE : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) ); break; case CP_CMD_ALLOW : case CP_CMD_DENY : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) ); break; case CP_CMD_UPDATE : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) ); break; case CP_CMD_MEDIA : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) ); break; case CP_CMD_GRPCHAT_CREATE : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) ); break; case CP_CMD_GRPCHAT_INVITE : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) ); break; case CP_CMD_EXTPROFILE_GET : case CP_CMD_EXTPROFILE_SET : mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) ); break; case CP_CMD_SPLASHCLICK : /* ignore error */ break; case CP_CMD_PING : case CP_CMD_POLL : break; default : mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) ); break; } return 0; } /*======================================================================================================================== * Low-level Packet receive */ #ifdef DEBUG_PROTOCOL /*------------------------------------------------------------------------ * Dump a received packet structure. * * @param p The received packet */ static void dump_packet( struct rx_packet* p ) { struct record* r = NULL; struct field* f = NULL; int i; int j; purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount ); for ( i = 0; i < p->rcount; i++ ) { r = p->records[i]; purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount ); for ( j = 0; j < r->fcount; j++ ) { f = r->fields[j]; purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data ); } } } #endif /*------------------------------------------------------------------------ * Free up memory used by a packet structure. * * @param p The received packet */ static void free_rx_packet( struct rx_packet* p ) { struct record* r = NULL; struct field* f = NULL; int i; int j; for ( i = 0; i < p->rcount; i++ ) { r = p->records[i]; for ( j = 0; j < r->fcount; j++ ) { g_free( f ); } g_free( r->fields ); g_free( r ); } g_free( p->records ); } /*------------------------------------------------------------------------ * Add a new field to a record. * * @param r Parent record object * @return The newly created field */ static struct field* add_field( struct record* r ) { struct field* field; field = g_new0( struct field, 1 ); r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) ); r->fields[r->fcount] = field; r->fcount++; return field; } /*------------------------------------------------------------------------ * Add a new record to a packet. * * @param p The packet object * @return The newly created record */ static struct record* add_record( struct rx_packet* p ) { struct record* rec; rec = g_new0( struct record, 1 ); p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) ); p->records[p->rcount] = rec; p->rcount++; return rec; } /*------------------------------------------------------------------------ * Parse the received byte stream into a proper client protocol packet. * * @param session The MXit session object * @return Success (0) or Failure (!0) */ int mxit_parse_packet( struct MXitSession* session ) { struct rx_packet packet; struct record* rec; struct field* field; gboolean pbreak; unsigned int i; int res = 0; #ifdef DEBUG_PROTOCOL purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i ); dump_bytes( session, session->rx_dbuf, session->rx_i ); #endif i = 0; while ( i < session->rx_i ) { /* create first record and field */ rec = NULL; field = NULL; memset( &packet, 0x00, sizeof( struct rx_packet ) ); rec = add_record( &packet ); pbreak = FALSE; /* break up the received packet into fields and records for easy parsing */ while ( ( i < session->rx_i ) && ( !pbreak ) ) { switch ( session->rx_dbuf[i] ) { case CP_SOCK_REC_TERM : /* new record */ if ( packet.rcount == 1 ) { /* packet command */ packet.cmd = atoi( packet.records[0]->fields[0]->data ); } else if ( packet.rcount == 2 ) { /* special case: binary multimedia packets should not be parsed here */ if ( packet.cmd == CP_CMD_MEDIA ) { /* add the chunked to new record */ rec = add_record( &packet ); field = add_field( rec ); field->data = &session->rx_dbuf[i + 1]; field->len = session->rx_i - i; /* now skip the binary data */ res = get_chunk_len( field->data ); /* determine if we have more packets */ if ( res + 6 + i < session->rx_i ) { /* we have more than one packet in this stream */ i += res + 6; pbreak = TRUE; } else { i = session->rx_i; } } } else if ( !field ) { field = add_field( rec ); field->data = &session->rx_dbuf[i]; } session->rx_dbuf[i] = '\0'; rec = add_record( &packet ); field = NULL; break; case CP_FLD_TERM : /* new field */ session->rx_dbuf[i] = '\0'; if ( !field ) { field = add_field( rec ); field->data = &session->rx_dbuf[i]; } field = NULL; break; case CP_PKT_TERM : /* packet is done! */ session->rx_dbuf[i] = '\0'; pbreak = TRUE; break; default : /* skip non special characters */ if ( !field ) { field = add_field( rec ); field->data = &session->rx_dbuf[i]; } field->len++; break; } i++; } if ( packet.rcount < 2 ) { /* bad packet */ purple_connection_error( session->con, _( "Invalid packet received from MXit." ) ); free_rx_packet( &packet ); continue; } session->rx_dbuf[session->rx_i] = '\0'; packet.errcode = atoi( packet.records[1]->fields[0]->data ); purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode ); #ifdef DEBUG_PROTOCOL /* debug */ dump_packet( &packet ); #endif /* reset the out ack */ if ( session->outack == packet.cmd ) { /* outstanding ack received from mxit server */ session->outack = 0; } /* check packet status */ if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) { /* error reply! */ if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) ) packet.errmsg = packet.records[1]->fields[1]->data; else packet.errmsg = NULL; res = process_error_response( session, &packet ); } else { /* success reply! */ res = process_success_response( session, &packet ); } /* free up the packet resources */ free_rx_packet( &packet ); } if ( session->outack == 0 ) mxit_manage_queue( session ); return res; } /*------------------------------------------------------------------------ * Callback when data is received from the MXit server. * * @param user_data The MXit session object * @param source The file-descriptor on which data was received * @param cond Condition which caused the callback (PURPLE_INPUT_READ) */ void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond ) { struct MXitSession* session = (struct MXitSession*) user_data; char ch; int res; int len; if ( session->rx_state == RX_STATE_RLEN ) { /* we are reading in the packet length */ len = read( session->fd, &ch, 1 ); if ( len < 0 ) { /* connection error */ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) ); return; } else if ( len == 0 ) { /* connection closed */ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) ); return; } else { /* byte read */ if ( ch == CP_REC_TERM ) { /* the end of the length record found */ session->rx_lbuf[session->rx_i] = '\0'; session->rx_res = atoi( &session->rx_lbuf[3] ); if ( session->rx_res > CP_MAX_PACKET ) { purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) ); } session->rx_state = RX_STATE_DATA; session->rx_i = 0; } else { /* still part of the packet length record */ session->rx_lbuf[session->rx_i] = ch; session->rx_i++; if ( session->rx_i >= sizeof( session->rx_lbuf ) ) { /* malformed packet length record (too long) */ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) ); return; } } } } else if ( session->rx_state == RX_STATE_DATA ) { /* we are reading in the packet data */ len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res ); if ( len < 0 ) { /* connection error */ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) ); return; } else if ( len == 0 ) { /* connection closed */ purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) ); return; } else { /* data read */ session->rx_i += len; session->rx_res -= len; if ( session->rx_res == 0 ) { /* ok, so now we have read in the whole packet */ session->rx_state = RX_STATE_PROC; } } } if ( session->rx_state == RX_STATE_PROC ) { /* we have a full packet, which we now need to process */ res = mxit_parse_packet( session ); if ( res == 0 ) { /* we are still logged in */ session->rx_state = RX_STATE_RLEN; session->rx_res = 0; session->rx_i = 0; } } } /*------------------------------------------------------------------------ * Log the user off MXit and close the connection * * @param session The MXit session object */ void mxit_close_connection( struct MXitSession* session ) { purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" ); if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) { /* we are already closed */ return; } else if ( session->flags & MXIT_FLAG_LOGGEDIN ) { /* we are currently logged in so we need to send a logout packet */ if ( !session->http ) { mxit_send_logout( session ); } session->flags &= ~MXIT_FLAG_LOGGEDIN; } session->flags &= ~MXIT_FLAG_CONNECTED; /* cancel outstanding HTTP request */ if ( ( session->http ) && ( session->http_out_req ) ) { purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req ); session->http_out_req = NULL; } /* remove the input cb function */ if ( session->con->inpa ) { purple_input_remove( session->con->inpa ); session->con->inpa = 0; } /* remove HTTP poll timer */ if ( session->http_timer_id > 0 ) purple_timeout_remove( session->http_timer_id ); /* remove queue manager timer */ if ( session->q_timer > 0 ) purple_timeout_remove( session->q_timer ); /* remove all groupchat rooms */ while ( session->rooms != NULL ) { struct multimx* multimx = (struct multimx *) session->rooms->data; session->rooms = g_list_remove( session->rooms, multimx ); free( multimx ); } g_list_free( session->rooms ); session->rooms = NULL; /* remove all rx chats names */ while ( session->active_chats != NULL ) { char* chat = (char*) session->active_chats->data; session->active_chats = g_list_remove( session->active_chats, chat ); g_free( chat ); } g_list_free( session->active_chats ); session->active_chats = NULL; /* free profile information */ if ( session->profile ) free( session->profile ); /* free custom emoticons */ mxit_free_emoticon_cache( session ); /* free allocated memory */ g_free( session->encpwd ); session->encpwd = NULL; /* flush all the commands still in the queue */ flush_queue( session ); }