comparison libpurple/protocols/mxit/protocol.c @ 28526:69aa4660401a

Initial addition of the MXit protocol plugin, provided by the MXit folks themselves.
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sun, 08 Nov 2009 23:55:56 +0000
parents
children 7d0b473f2295
comparison
equal deleted inserted replaced
28525:13e668ef158d 28526:69aa4660401a
1 /*
2 * MXit Protocol libPurple Plugin
3 *
4 * -- MXit client protocol implementation --
5 *
6 * Pieter Loubser <libpurple@mxit.com>
7 *
8 * (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
9 * <http://www.mxitlifestyle.com>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 */
25
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <errno.h>
30
31 #include "purple.h"
32
33 #include "protocol.h"
34 #include "mxit.h"
35 #include "roster.h"
36 #include "chunk.h"
37 #include "filexfer.h"
38 #include "markup.h"
39 #include "multimx.h"
40 #include "splashscreen.h"
41 #include "login.h"
42 #include "formcmds.h"
43 #include "http.h"
44
45
46 #define MXIT_MS_OFFSET 3
47
48 /* configure the right record terminator char to use */
49 #define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
50
51
52
53 /*------------------------------------------------------------------------
54 * Display a notification popup message to the user.
55 *
56 * @param type The type of notification:
57 * - info: PURPLE_NOTIFY_MSG_INFO
58 * - warning: PURPLE_NOTIFY_MSG_WARNING
59 * - error: PURPLE_NOTIFY_MSG_ERROR
60 * @param heading Heading text
61 * @param message Message text
62 */
63 void mxit_popup( int type, const char* heading, const char* message )
64 {
65 /* (reference: "libpurple/notify.h") */
66 purple_notify_message( NULL, type, _( MXIT_POPUP_WIN_NAME ), heading, message, NULL, NULL );
67 }
68
69
70 /*------------------------------------------------------------------------
71 * For compatibility with legacy clients, all usernames are sent from MXit with a domain
72 * appended. For MXit contacts, this domain is set to "@m". This function strips
73 * those fake domains.
74 *
75 * @param username The username of the contact
76 */
77 void mxit_strip_domain( char* username )
78 {
79 if ( g_str_has_suffix( username, "@m" ) )
80 username[ strlen(username) - 2 ] = '\0';
81 }
82
83
84 /*------------------------------------------------------------------------
85 * Dump a byte buffer to the console for debugging purposes.
86 *
87 * @param buf The data
88 * @param len The data length
89 */
90 void dump_bytes( struct MXitSession* session, const char* buf, int len )
91 {
92 char msg[( len * 3 ) + 1];
93 int i;
94
95 memset( msg, 0x00, sizeof( msg ) );
96
97 for ( i = 0; i < len; i++ ) {
98 if ( buf[i] == CP_REC_TERM ) /* record terminator */
99 msg[i] = '!';
100 else if ( buf[i] == CP_FLD_TERM ) /* field terminator */
101 msg[i] = '^';
102 else if ( buf[i] == CP_PKT_TERM ) /* packet terminator */
103 msg[i] = '@';
104 else if ( buf[i] < 0x20 )
105 msg[i] = '_';
106 else
107 msg[i] = buf[i];
108
109 }
110
111 purple_debug_info( MXIT_PLUGIN_ID, "DUMP: '%s'\n", msg );
112 }
113
114
115 /*------------------------------------------------------------------------
116 * Determine if we have an active chat with a specific contact
117 *
118 * @param session The MXit session object
119 * @param who The contact name
120 * @return Return true if we have an active chat with the contact
121 */
122 gboolean find_active_chat( const GList* chats, const char* who )
123 {
124 const GList* list = chats;
125 const char* chat = NULL;
126
127 while ( list ) {
128 chat = (const char*) list->data;
129
130 if ( strcmp( chat, who ) == 0 )
131 return TRUE;
132
133 list = g_list_next( list );
134 }
135
136 return FALSE;
137 }
138
139
140 /*========================================================================================================================
141 * Low-level Packet transmission
142 */
143
144 /*------------------------------------------------------------------------
145 * Remove next packet from transmission queue.
146 *
147 * @param session The MXit session object
148 * @return The next packet for transmission (or NULL)
149 */
150 static struct tx_packet* pop_tx_packet( struct MXitSession* session )
151 {
152 struct tx_packet* packet = NULL;
153
154 if ( session->queue.count > 0 ) {
155 /* dequeue the next packet */
156 packet = session->queue.packets[session->queue.rd_i];
157 session->queue.packets[session->queue.rd_i] = NULL;
158 session->queue.rd_i = ( session->queue.rd_i + 1 ) % MAX_QUEUE_SIZE;
159 session->queue.count--;
160 }
161
162 return packet;
163 }
164
165
166 /*------------------------------------------------------------------------
167 * Add packet to transmission queue.
168 *
169 * @param session The MXit session object
170 * @param packet The packet to transmit
171 * @return Return TRUE if packet was enqueue, or FALSE if queue is full.
172 */
173 static gboolean push_tx_packet( struct MXitSession* session, struct tx_packet* packet )
174 {
175 if ( session->queue.count < MAX_QUEUE_SIZE ) {
176 /* enqueue packet */
177 session->queue.packets[session->queue.wr_i] = packet;
178 session->queue.wr_i = ( session->queue.wr_i + 1 ) % MAX_QUEUE_SIZE;
179 session->queue.count++;
180 return TRUE;
181 }
182 else
183 return FALSE; /* queue is full */
184 }
185
186
187 /*------------------------------------------------------------------------
188 * Deallocate transmission packet.
189 *
190 * @param packet The packet to deallocate.
191 */
192 static void free_tx_packet( struct tx_packet* packet )
193 {
194 g_free( packet->data );
195 g_free( packet );
196 packet = NULL;
197 }
198
199
200 /*------------------------------------------------------------------------
201 * Flush all the packets from the tx queue and release the resources.
202 *
203 * @param session The MXit session object
204 */
205 static void flush_queue( struct MXitSession* session )
206 {
207 struct tx_packet* packet;
208
209 purple_debug_info( MXIT_PLUGIN_ID, "flushing the tx queue\n" );
210
211 while ( (packet = pop_tx_packet( session ) ) != NULL )
212 free_tx_packet( packet );
213 }
214
215
216 /*------------------------------------------------------------------------
217 * TX Step 3: Write the packet data to the TCP connection.
218 *
219 * @param fd The file descriptor
220 * @param pktdata The packet data
221 * @param pktlen The length of the packet data
222 * @return Return -1 on error, otherwise 0
223 */
224 static int mxit_write_sock_packet( int fd, const char* pktdata, int pktlen )
225 {
226 int written;
227 int res;
228
229 written = 0;
230 while ( written < pktlen ) {
231 res = write( fd, &pktdata[written], pktlen - written );
232 if ( res <= 0 ) {
233 /* error on socket */
234 if ( errno == EAGAIN )
235 continue;
236
237 purple_debug_error( MXIT_PLUGIN_ID, "Error while writing packet to MXit server (%i)\n", res );
238 return -1;
239 }
240 written += res;
241 }
242
243 return 0;
244 }
245
246
247 /*------------------------------------------------------------------------
248 * Callback called for handling a HTTP GET response
249 *
250 * @param url_data libPurple internal object (see purple_util_fetch_url_request)
251 * @param user_data The MXit session object
252 * @param url_text The data returned (could be NULL if error)
253 * @param len The length of the data returned (0 if error)
254 * @param error_message Descriptive error message
255 */
256 static void mxit_cb_http_rx( PurpleUtilFetchUrlData* url_data, gpointer user_data, const gchar* url_text, gsize len, const gchar* error_message )
257 {
258 struct MXitSession* session = (struct MXitSession*) user_data;
259
260 /* clear outstanding request */
261 session->http_out_req = NULL;
262
263 if ( ( !url_text ) || ( len == 0 ) ) {
264 /* error with request */
265 purple_debug_error( MXIT_PLUGIN_ID, "HTTP response error (%s)\n", error_message );
266 return;
267 }
268
269 /* convert the HTTP result */
270 memcpy( session->rx_dbuf, url_text, len );
271 session->rx_i = len;
272
273 mxit_parse_packet( session );
274 }
275
276
277 /*------------------------------------------------------------------------
278 * TX Step 3: Write the packet data to the HTTP connection (GET style).
279 *
280 * @param session The MXit session object
281 * @param pktdata The packet data
282 * @param pktlen The length of the packet data
283 * @return Return -1 on error, otherwise 0
284 */
285 static void mxit_write_http_get( struct MXitSession* session, struct tx_packet* packet )
286 {
287 char* part = NULL;
288 char* url = NULL;
289
290 if ( packet->datalen > 0 ) {
291 char* tmp = NULL;
292
293 tmp = g_strndup( packet->data, packet->datalen );
294 part = g_strdup( purple_url_encode( tmp ) );
295 g_free( tmp );
296 }
297
298 url = g_strdup_printf( "%s?%s%s", session->http_server, purple_url_encode( packet->header ), ( !part ) ? "" : part );
299
300 #ifdef DEBUG_PROTOCOL
301 purple_debug_info( MXIT_PLUGIN_ID, "HTTP GET: '%s'\n", url );
302 #endif
303
304 /* send the HTTP request */
305 session->http_out_req = purple_util_fetch_url_request( url, TRUE, MXIT_HTTP_USERAGENT, TRUE, NULL, FALSE, mxit_cb_http_rx, session );
306
307 g_free( url );
308 if ( part )
309 g_free( part );
310 }
311
312
313 /*------------------------------------------------------------------------
314 * TX Step 3: Write the packet data to the HTTP connection (POST style).
315 *
316 * @param session The MXit session object
317 * @param pktdata The packet data
318 * @param pktlen The length of the packet data
319 * @return Return -1 on error, otherwise 0
320 */
321 static void mxit_write_http_post( struct MXitSession* session, struct tx_packet* packet )
322 {
323 char request[256 + packet->datalen];
324 int reqlen;
325 char* host_name;
326 int host_port;
327 gboolean ok;
328
329 /* extract the HTTP host name and host port number to connect to */
330 ok = purple_url_parse( session->http_server, &host_name, &host_port, NULL, NULL, NULL );
331 if ( !ok ) {
332 purple_debug_error( MXIT_PLUGIN_ID, "HTTP POST error: (host name '%s' not valid)\n", session->http_server );
333 }
334
335 /* strip off the last '&' from the header */
336 packet->header[packet->headerlen - 1] = '\0';
337 packet->headerlen--;
338
339 /* build the HTTP request packet */
340 reqlen = g_snprintf( request, 256,
341 "POST %s?%s HTTP/1.1\r\n"
342 "User-Agent: " MXIT_HTTP_USERAGENT "\r\n"
343 "Content-Type: application/octet-stream\r\n"
344 "Host: %s\r\n"
345 "Content-Length: %" G_GSIZE_FORMAT "\r\n"
346 "\r\n",
347 session->http_server,
348 purple_url_encode( packet->header ),
349 host_name,
350 packet->datalen - MXIT_MS_OFFSET
351 );
352
353 /* copy over the packet body data (could be binary) */
354 memcpy( request + reqlen, packet->data + MXIT_MS_OFFSET, packet->datalen - MXIT_MS_OFFSET );
355 reqlen += packet->datalen;
356
357 #ifdef DEBUG_PROTOCOL
358 purple_debug_info( MXIT_PLUGIN_ID, "HTTP POST:\n" );
359 dump_bytes( session, request, reqlen );
360 #endif
361
362 /* send the request to the HTTP server */
363 mxit_http_send_request( session, host_name, host_port, request, reqlen );
364 }
365
366
367 /*------------------------------------------------------------------------
368 * TX Step 2: Handle the transmission of the packet to the MXit server.
369 *
370 * @param session The MXit session object
371 * @param packet The packet to transmit
372 */
373 static void mxit_send_packet( struct MXitSession* session, struct tx_packet* packet )
374 {
375 int res;
376
377 if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
378 /* we are not connected so ignore all packets to be send */
379 purple_debug_error( MXIT_PLUGIN_ID, "Dropping TX packet (we are not connected)\n" );
380 return;
381 }
382
383 purple_debug_info( MXIT_PLUGIN_ID, "Packet send CMD:%i (%i)\n", packet->cmd, packet->headerlen + packet->datalen );
384 #ifdef DEBUG_PROTOCOL
385 dump_bytes( session, packet->header, packet->headerlen );
386 dump_bytes( session, packet->data, packet->datalen );
387 #endif
388
389 if ( !session->http ) {
390 /* socket connection */
391 char data[packet->datalen + packet->headerlen];
392 int datalen;
393
394 /* create raw data buffer */
395 memcpy( data, packet->header, packet->headerlen );
396 memcpy( data + packet->headerlen, packet->data, packet->datalen );
397 datalen = packet->headerlen + packet->datalen;
398
399 res = mxit_write_sock_packet( session->fd, data, datalen );
400 if ( res < 0 ) {
401 /* we must have lost the connection, so terminate it so that we can reconnect */
402 purple_connection_error( session->con, _( "We have lost the connection to MXit. Please reconnect." ) );
403 }
404 }
405 else {
406 /* http connection */
407
408 if ( packet->cmd == CP_CMD_MEDIA ) {
409 /* multimedia packets must be send with a HTTP POST */
410 mxit_write_http_post( session, packet );
411 }
412 else {
413 mxit_write_http_get( session, packet );
414 }
415 }
416
417 /* update the timestamp of the last-transmitted packet */
418 session->last_tx = time( NULL );
419
420 /*
421 * we need to remember that we are still waiting for the ACK from
422 * the server on this request
423 */
424 session->outack = packet->cmd;
425
426 /* free up the packet resources */
427 free_tx_packet( packet );
428 }
429
430
431 /*------------------------------------------------------------------------
432 * TX Step 1: Create a new Tx packet and queue it for sending.
433 *
434 * @param session The MXit session object
435 * @param data The packet data (payload)
436 * @param datalen The length of the packet data
437 * @param cmd The MXit command for this packet
438 */
439 static void mxit_queue_packet( struct MXitSession* session, const char* data, int datalen, int cmd )
440 {
441 struct tx_packet* packet;
442 char header[256];
443 int hlen;
444
445 /* create a packet for sending */
446 packet = g_new0( struct tx_packet, 1 );
447 packet->data = g_malloc0( datalen );
448 packet->cmd = cmd;
449 packet->headerlen = 0;
450
451 /* create generic packet header */
452 hlen = sprintf( header, "id=%s%c", session->acc->username, CP_REC_TERM ); /* client msisdn */
453
454 if ( session->http ) {
455 /* http connection only */
456 hlen += sprintf( header + hlen, "s=" );
457 if ( session->http_sesid > 0 ) {
458 hlen += sprintf( header + hlen, "%u%c", session->http_sesid, CP_FLD_TERM ); /* http session id */
459 }
460 session->http_seqno++;
461 hlen += sprintf( header + hlen, "%u%c", session->http_seqno, CP_REC_TERM ); /* http request sequence id */
462 }
463
464 hlen += sprintf( header + hlen, "cm=%i%c", cmd, CP_REC_TERM ); /* packet command */
465
466 if ( !session->http ) {
467 /* socket connection only */
468 packet->headerlen += sprintf( packet->header, "ln=%i%c", ( datalen + hlen ), CP_REC_TERM ); /* packet length */
469 }
470
471 /* copy the header to packet */
472 memcpy( packet->header + packet->headerlen, header, hlen );
473 packet->headerlen += hlen;
474
475 /* copy payload to packet */
476 if ( datalen > 0 )
477 memcpy( packet->data, data, datalen );
478 packet->datalen = datalen;
479
480
481 /*
482 * shortcut: first check if there are any commands still outstanding.
483 * if not, then we might as well just write this packet directly and
484 * skip the whole queueing thing
485 */
486 if ( session->outack == 0 ) {
487 /* no outstanding ACKs, so we might as well write it directly */
488 mxit_send_packet( session, packet );
489 }
490 else {
491 /* ACK still outstanding, so we need to queue this request until we have the ACK */
492
493 if ( ( packet->cmd == CP_CMD_PING ) || ( packet->cmd == CP_CMD_POLL ) ) {
494 /* we do NOT queue HTTP poll nor socket ping packets */
495 free_tx_packet( packet );
496 return;
497 }
498
499 purple_debug_info( MXIT_PLUGIN_ID, "queueing packet for later sending cmd=%i\n", cmd );
500 if ( !push_tx_packet( session, packet ) ) {
501 /* packet could not be queued for transmission */
502 mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Message Send Error" ), _( "Unable to process your request at this time" ) );
503 free_tx_packet( packet );
504 }
505 }
506 }
507
508
509 /*------------------------------------------------------------------------
510 * Callback to manage the packet send queue (send next packet, timeout's, etc).
511 *
512 * @param session The MXit session object
513 */
514 gboolean mxit_manage_queue( gpointer user_data )
515 {
516 struct MXitSession* session = (struct MXitSession*) user_data;
517 struct tx_packet* packet = NULL;
518
519 if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
520 /* we are not connected, so ignore the queue */
521 return TRUE;
522 }
523 else if ( session->outack > 0 ) {
524 /* we are still waiting for an outstanding ACK from the MXit server */
525 if ( session->last_tx <= time( NULL ) - MXIT_ACK_TIMEOUT ) {
526 /* ack timeout! so we close the connection here */
527 purple_debug_info( MXIT_PLUGIN_ID, "mxit_manage_queue: Timeout awaiting ACK for command '%X'\n", session->outack );
528 purple_connection_error( session->con, _( "Timeout while waiting for a response from the MXit server." ) );
529 }
530 return TRUE;
531 }
532
533 packet = pop_tx_packet( session );
534 if ( packet != NULL ) {
535 /* there was a packet waiting to be sent to the server, now is the time to do something about it */
536
537 /* send the packet to MXit server */
538 mxit_send_packet( session, packet );
539 }
540
541 return TRUE;
542 }
543
544
545 /*------------------------------------------------------------------------
546 * Callback to manage HTTP server polling (HTTP connections ONLY)
547 *
548 * @param session The MXit session object
549 */
550 gboolean mxit_manage_polling( gpointer user_data )
551 {
552 struct MXitSession* session = (struct MXitSession*) user_data;
553 gboolean poll = FALSE;
554 time_t now = time( NULL );
555 int polldiff;
556 int rxdiff;
557
558 if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
559 /* we only poll if we are actually logged in */
560 return TRUE;
561 }
562
563 /* calculate the time differences */
564 rxdiff = now - session->last_rx;
565 polldiff = now - session->http_last_poll;
566
567 if ( rxdiff < MXIT_HTTP_POLL_MIN ) {
568 /* we received some reply a few moments ago, so reset the poll interval */
569 session->http_interval = MXIT_HTTP_POLL_MIN;
570 }
571 else if ( session->http_last_poll < ( now - session->http_interval ) ) {
572 /* time to poll again */
573 poll = TRUE;
574
575 /* back-off some more with the polling */
576 session->http_interval = session->http_interval + ( session->http_interval / 2 );
577 if ( session->http_interval > MXIT_HTTP_POLL_MAX )
578 session->http_interval = MXIT_HTTP_POLL_MAX;
579 }
580
581 /* debugging */
582 //purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i,%i)\n", session->http_interval, rxdiff, polldiff );
583
584 if ( poll ) {
585 /* send poll request */
586 session->http_last_poll = time( NULL );
587 mxit_send_poll( session );
588 }
589
590 return TRUE;
591 }
592
593
594 /*========================================================================================================================
595 * Send MXit operations.
596 */
597
598 /*------------------------------------------------------------------------
599 * Send a ping/keepalive packet to MXit server.
600 *
601 * @param session The MXit session object
602 */
603 void mxit_send_ping( struct MXitSession* session )
604 {
605 /* queue packet for transmission */
606 mxit_queue_packet( session, NULL, 0, CP_CMD_PING );
607 }
608
609
610 /*------------------------------------------------------------------------
611 * Send a poll request to the HTTP server (HTTP connections ONLY).
612 *
613 * @param session The MXit session object
614 */
615 void mxit_send_poll( struct MXitSession* session )
616 {
617 /* queue packet for transmission */
618 mxit_queue_packet( session, NULL, 0, CP_CMD_POLL );
619 }
620
621
622 /*------------------------------------------------------------------------
623 * Send a logout packet to the MXit server.
624 *
625 * @param session The MXit session object
626 */
627 void mxit_send_logout( struct MXitSession* session )
628 {
629 /* queue packet for transmission */
630 mxit_queue_packet( session, NULL, 0, CP_CMD_LOGOUT );
631 }
632
633
634 /*------------------------------------------------------------------------
635 * Send a register packet to the MXit server.
636 *
637 * @param session The MXit session object
638 */
639 void mxit_send_register( struct MXitSession* session )
640 {
641 struct MXitProfile* profile = session->profile;
642 const char* locale;
643 char data[CP_MAX_PACKET];
644 int datalen;
645
646 locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
647
648 /* convert the packet to a byte stream */
649 datalen = sprintf( data, "ms=%s%c%s%c%i%c%s%c" /* "ms"=password\1version\1maxreplyLen\1name\1 */
650 "%s%c%i%c%s%c%s%c" /* dateOfBirth\1gender\1location\1capabilities\1 */
651 "%s%c%i%c%s%c%s", /* dc\1features\1dialingcode\1locale */
652 session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, CP_MAX_FILESIZE, CP_FLD_TERM, profile->nickname, CP_FLD_TERM,
653 profile->birthday, CP_FLD_TERM, ( profile->male ) ? 1 : 0, CP_FLD_TERM, MXIT_DEFAULT_LOC, CP_FLD_TERM, MXIT_CP_CAP, CP_FLD_TERM,
654 session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM, session->dialcode, CP_FLD_TERM, locale
655 );
656
657 /* queue packet for transmission */
658 mxit_queue_packet( session, data, datalen, CP_CMD_REGISTER );
659 }
660
661
662 /*------------------------------------------------------------------------
663 * Send a login packet to the MXit server.
664 *
665 * @param session The MXit session object
666 */
667 void mxit_send_login( struct MXitSession* session )
668 {
669 const char* splashId;
670 const char* locale;
671 char data[CP_MAX_PACKET];
672 int datalen;
673
674 locale = purple_account_get_string( session->acc, MXIT_CONFIG_LOCALE, MXIT_DEFAULT_LOCALE );
675
676 /* convert the packet to a byte stream */
677 datalen = sprintf( data, "ms=%s%c%s%c%i%c" /* "ms"=password\1version\1getContacts\1 */
678 "%s%c%s%c%i%c" /* capabilities\1dc\1features\1 */
679 "%s%c%s", /* dialingcode\1locale */
680 session->encpwd, CP_FLD_TERM, MXIT_CP_VERSION, CP_FLD_TERM, 1, CP_FLD_TERM,
681 MXIT_CP_CAP, CP_FLD_TERM, session->distcode, CP_FLD_TERM, MXIT_CP_FEATURES, CP_FLD_TERM,
682 session->dialcode, CP_FLD_TERM, locale
683 );
684
685 /* include "custom resource" information */
686 splashId = splash_current( session );
687 if ( splashId != NULL )
688 datalen += sprintf( data + datalen, "%ccr=%s", CP_REC_TERM, splashId );
689
690 /* queue packet for transmission */
691 mxit_queue_packet( session, data, datalen, CP_CMD_LOGIN );
692 }
693
694
695 /*------------------------------------------------------------------------
696 * Send a chat message packet to the MXit server.
697 *
698 * @param session The MXit session object
699 * @param to The username of the recipient
700 * @param msg The message text
701 */
702 void mxit_send_message( struct MXitSession* session, const char* to, const char* msg, gboolean parse_markup )
703 {
704 char data[CP_MAX_PACKET];
705 char* markuped_msg;
706 int datalen;
707 int msgtype = CP_MSGTYPE_NORMAL;
708
709 /* first we need to convert the markup from libPurple to MXit format */
710 if ( parse_markup )
711 markuped_msg = mxit_convert_markup_tx( msg, &msgtype );
712 else
713 markuped_msg = g_strdup( msg );
714
715 /* convert the packet to a byte stream */
716 datalen = sprintf( data, "ms=%s%c%s%c%i%c%i", /* "ms"=jid\1msg\1type\1flags */
717 to, CP_FLD_TERM, markuped_msg, CP_FLD_TERM, msgtype, CP_FLD_TERM, CP_MSG_MARKUP | CP_MSG_EMOTICON
718 );
719
720 /* free the resources */
721 g_free( markuped_msg );
722
723 /* queue packet for transmission */
724 mxit_queue_packet( session, data, datalen, CP_CMD_TX_MSG );
725 }
726
727
728 /*------------------------------------------------------------------------
729 * Send a extended profile request packet to the MXit server.
730 *
731 * @param session The MXit session object
732 * @param username Username who's profile is being requested (NULL = our own)
733 * @param nr_attribs Number of attributes being requested
734 * @param attributes The names of the attributes
735 */
736 void mxit_send_extprofile_request( struct MXitSession* session, const char* username, unsigned int nr_attrib, const char* attribute[] )
737 {
738 char data[CP_MAX_PACKET];
739 int datalen;
740 unsigned int i;
741
742 datalen = sprintf( data, "ms=%s%c%i", /* "ms="mxitid\1nr_attributes */
743 (username ? username : ""), CP_FLD_TERM, nr_attrib);
744
745 /* add attributes */
746 for ( i = 0; i < nr_attrib; i++ )
747 datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, attribute[i] );
748
749 /* queue packet for transmission */
750 mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_GET );
751 }
752
753
754 /*------------------------------------------------------------------------
755 * Send an update profile packet to the MXit server.
756 *
757 * @param session The MXit session object
758 * @param password The new password to be used for logging in (optional)
759 * @param nr_attrib The number of attributes
760 * @param attributes String containing the attributes and settings seperated by '0x01'
761 */
762 void mxit_send_extprofile_update( struct MXitSession* session, const char* password, unsigned int nr_attrib, const char* attributes )
763 {
764 char data[CP_MAX_PACKET];
765 gchar** parts;
766 int datalen;
767 unsigned int i;
768
769 parts = g_strsplit( attributes, "\01", ( MXIT_MAX_ATTRIBS * 3 ) );
770
771 /* convert the packet to a byte stream */
772 datalen = sprintf( data, "ms=%s%c%i", /* "ms"=password\1nr_attibutes */
773 ( password ) ? password : "", CP_FLD_TERM, nr_attrib
774 );
775
776 /* add attributes */
777 for ( i = 1; i < nr_attrib * 3; i+=3 )
778 datalen += sprintf( data + datalen, "%c%s%c%s%c%s", /* \1name\1type\1value */
779 CP_FLD_TERM, parts[i], CP_FLD_TERM, parts[i + 1], CP_FLD_TERM, parts[i + 2] );
780
781 /* queue packet for transmission */
782 mxit_queue_packet( session, data, datalen, CP_CMD_EXTPROFILE_SET );
783
784 /* freeup the memory */
785 g_strfreev( parts );
786 }
787
788
789 /*------------------------------------------------------------------------
790 * Send a presence update packet to the MXit server.
791 *
792 * @param session The MXit session object
793 * @param presence The presence (as per MXit types)
794 * @param statusmsg The status message (can be NULL)
795 */
796 void mxit_send_presence( struct MXitSession* session, int presence, const char* statusmsg )
797 {
798 char data[CP_MAX_PACKET];
799 int datalen;
800
801 /* convert the packet to a byte stream */
802 datalen = sprintf( data, "ms=%i%c", /* "ms"=show\1status */
803 presence, CP_FLD_TERM
804 );
805
806 /* append status message (if one is set) */
807 if ( statusmsg )
808 datalen += sprintf( data + datalen, "%s", statusmsg );
809
810 /* queue packet for transmission */
811 mxit_queue_packet( session, data, datalen, CP_CMD_STATUS );
812 }
813
814
815 /*------------------------------------------------------------------------
816 * Send a mood update packet to the MXit server.
817 *
818 * @param session The MXit session object
819 * @param mood The mood (as per MXit types)
820 */
821 void mxit_send_mood( struct MXitSession* session, int mood )
822 {
823 char data[CP_MAX_PACKET];
824 int datalen;
825
826 /* convert the packet to a byte stream */
827 datalen = sprintf( data, "ms=%i", /* "ms"=mood */
828 mood
829 );
830
831 /* queue packet for transmission */
832 mxit_queue_packet( session, data, datalen, CP_CMD_MOOD );
833 }
834
835
836 /*------------------------------------------------------------------------
837 * Send an invite contact packet to the MXit server.
838 *
839 * @param session The MXit session object
840 * @param username The username of the contact being invited
841 * @param alias Our alias for the contact
842 * @param groupname Group in which contact should be stored.
843 */
844 void mxit_send_invite( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
845 {
846 char data[CP_MAX_PACKET];
847 int datalen;
848
849 /* convert the packet to a byte stream */
850 datalen = sprintf( data, "ms=%s%c%s%c%s%c%i%c%s", /* "ms"=group\1username\1alias\1type\1msg */
851 groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias,
852 CP_FLD_TERM, MXIT_TYPE_MXIT, CP_FLD_TERM, ""
853 );
854
855 /* queue packet for transmission */
856 mxit_queue_packet( session, data, datalen, CP_CMD_INVITE );
857 }
858
859
860 /*------------------------------------------------------------------------
861 * Send a remove contact packet to the MXit server.
862 *
863 * @param session The MXit session object
864 * @param username The username of the contact being removed
865 */
866 void mxit_send_remove( struct MXitSession* session, const char* username )
867 {
868 char data[CP_MAX_PACKET];
869 int datalen;
870
871 /* convert the packet to a byte stream */
872 datalen = sprintf( data, "ms=%s", /* "ms"=username */
873 username
874 );
875
876 /* queue packet for transmission */
877 mxit_queue_packet( session, data, datalen, CP_CMD_REMOVE );
878 }
879
880
881 /*------------------------------------------------------------------------
882 * Send an accept subscription (invite) packet to the MXit server.
883 *
884 * @param session The MXit session object
885 * @param username The username of the contact being accepted
886 * @param alias Our alias for the contact
887 */
888 void mxit_send_allow_sub( struct MXitSession* session, const char* username, const char* alias )
889 {
890 char data[CP_MAX_PACKET];
891 int datalen;
892
893 /* convert the packet to a byte stream */
894 datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=username\1group\1alias */
895 username, CP_FLD_TERM, "", CP_FLD_TERM, alias
896 );
897
898 /* queue packet for transmission */
899 mxit_queue_packet( session, data, datalen, CP_CMD_ALLOW );
900 }
901
902
903 /*------------------------------------------------------------------------
904 * Send an deny subscription (invite) packet to the MXit server.
905 *
906 * @param session The MXit session object
907 * @param username The username of the contact being denied
908 */
909 void mxit_send_deny_sub( struct MXitSession* session, const char* username )
910 {
911 char data[CP_MAX_PACKET];
912 int datalen;
913
914 /* convert the packet to a byte stream */
915 datalen = sprintf( data, "ms=%s", /* "ms"=username */
916 username
917 );
918
919 /* queue packet for transmission */
920 mxit_queue_packet( session, data, datalen, CP_CMD_DENY );
921 }
922
923
924 /*------------------------------------------------------------------------
925 * Send an update contact packet to the MXit server.
926 *
927 * @param session The MXit session object
928 * @param username The username of the contact being denied
929 * @param alias Our alias for the contact
930 * @param groupname Group in which contact should be stored.
931 */
932 void mxit_send_update_contact( struct MXitSession* session, const char* username, const char* alias, const char* groupname )
933 {
934 char data[CP_MAX_PACKET];
935 int datalen;
936
937 /* convert the packet to a byte stream */
938 datalen = sprintf( data, "ms=%s%c%s%c%s", /* "ms"=groupname\1username\1alias */
939 groupname, CP_FLD_TERM, username, CP_FLD_TERM, alias
940 );
941
942 /* queue packet for transmission */
943 mxit_queue_packet( session, data, datalen, CP_CMD_UPDATE );
944 }
945
946
947 /*------------------------------------------------------------------------
948 * Send a splash-screen click event packet.
949 *
950 * @param session The MXit session object
951 * @param splashid The identifier of the splash-screen
952 */
953 void mxit_send_splashclick( struct MXitSession* session, const char* splashid )
954 {
955 char data[CP_MAX_PACKET];
956 int datalen;
957
958 /* convert the packet to a byte stream */
959 datalen = sprintf( data, "ms=%s", /* "ms"=splashId */
960 splashid
961 );
962
963 /* queue packet for transmission */
964 mxit_queue_packet( session, data, datalen, CP_CMD_SPLASHCLICK );
965 }
966
967
968 /*------------------------------------------------------------------------
969 * Send packet to create a MultiMX room.
970 *
971 * @param session The MXit session object
972 * @param groupname Name of the room to create
973 * @param nr_usernames Number of users in initial invite
974 * @param usernames The usernames of the users in the initial invite
975 */
976 void mxit_send_groupchat_create( struct MXitSession* session, const char* groupname, int nr_usernames, const char* usernames[] )
977 {
978 char data[CP_MAX_PACKET];
979 int datalen;
980 int i;
981
982 /* convert the packet to a byte stream */
983 datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
984 groupname, CP_FLD_TERM, nr_usernames
985 );
986
987 /* add usernames */
988 for ( i = 0; i < nr_usernames; i++ )
989 datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
990
991 /* queue packet for transmission */
992 mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_CREATE );
993 }
994
995
996 /*------------------------------------------------------------------------
997 * Send packet to invite users to existing MultiMX room.
998 *
999 * @param session The MXit session object
1000 * @param roomid The unique RoomID for the MultiMx room.
1001 * @param nr_usernames Number of users being invited
1002 * @param usernames The usernames of the users being invited
1003 */
1004
1005 void mxit_send_groupchat_invite( struct MXitSession* session, const char* roomid, int nr_usernames, const char* usernames[] )
1006 {
1007 char data[CP_MAX_PACKET];
1008 int datalen;
1009 int i;
1010
1011 /* convert the packet to a byte stream */
1012 datalen = sprintf( data, "ms=%s%c%i", /* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
1013 roomid, CP_FLD_TERM, nr_usernames
1014 );
1015
1016 /* add usernames */
1017 for ( i = 0; i < nr_usernames; i++ )
1018 datalen += sprintf( data + datalen, "%c%s", CP_FLD_TERM, usernames[i] );
1019
1020 /* queue packet for transmission */
1021 mxit_queue_packet( session, data, datalen, CP_CMD_GRPCHAT_INVITE );
1022 }
1023
1024
1025 /*------------------------------------------------------------------------
1026 * Send a "send file direct" multimedia packet.
1027 *
1028 * @param session The MXit session object
1029 * @param username The username of the recipient
1030 * @param filename The name of the file being sent
1031 * @param buf The content of the file
1032 * @param buflen The length of the file contents
1033 */
1034 void mxit_send_file( struct MXitSession* session, const char* username, const char* filename, const unsigned char* buf, int buflen )
1035 {
1036 char data[CP_MAX_PACKET];
1037 int datalen = 0;
1038 struct raw_chunk* chunk;
1039 int size;
1040
1041 purple_debug_info( MXIT_PLUGIN_ID, "SENDING FILE '%s' of %i bytes to user '%s'\n", filename, buflen, username );
1042
1043 /* convert the packet to a byte stream */
1044 datalen = sprintf( data, "ms=" );
1045
1046 /* map chunk header over data buffer */
1047 chunk = (struct raw_chunk *) &data[datalen];
1048
1049 size = mxit_chunk_create_senddirect( chunk->data, username, filename, buf, buflen );
1050 if ( size < 0 ) {
1051 purple_debug_error( MXIT_PLUGIN_ID, "Error creating senddirect chunk (%i)\n", size );
1052 return;
1053 }
1054
1055 chunk->type = CP_CHUNK_DIRECT_SND;
1056 chunk->length = htonl( size );
1057 datalen += sizeof( struct raw_chunk ) + size;
1058
1059 /* send the byte stream to the mxit server */
1060 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1061 }
1062
1063
1064 /*------------------------------------------------------------------------
1065 * Send a "reject file" multimedia packet.
1066 *
1067 * @param session The MXit session object
1068 * @param fileid A unique ID that identifies this file
1069 */
1070 void mxit_send_file_reject( struct MXitSession* session, const char* fileid )
1071 {
1072 char data[CP_MAX_PACKET];
1073 int datalen = 0;
1074 struct raw_chunk* chunk;
1075 int size;
1076
1077 purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_reject\n" );
1078
1079 /* convert the packet to a byte stream */
1080 datalen = sprintf( data, "ms=" );
1081
1082 /* map chunk header over data buffer */
1083 chunk = (struct raw_chunk *) &data[datalen];
1084
1085 size = mxit_chunk_create_reject( chunk->data, fileid );
1086 if ( size < 0 ) {
1087 purple_debug_error( MXIT_PLUGIN_ID, "Error creating reject chunk (%i)\n", size );
1088 return;
1089 }
1090
1091 chunk->type = CP_CHUNK_REJECT;
1092 chunk->length = htonl( size );
1093 datalen += sizeof( struct raw_chunk ) + size;
1094
1095 /* send the byte stream to the mxit server */
1096 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1097 }
1098
1099
1100 /*------------------------------------------------------------------------
1101 * Send a "get file" multimedia packet.
1102 *
1103 * @param session The MXit session object
1104 * @param fileid A unique ID that identifies this file
1105 * @param filesize The number of bytes to retrieve
1106 * @param offset Offset in file at which to start retrieving
1107 */
1108 void mxit_send_file_accept( struct MXitSession* session, const char* fileid, int filesize, int offset )
1109 {
1110 char data[CP_MAX_PACKET];
1111 int datalen = 0;
1112 struct raw_chunk* chunk;
1113 int size;
1114
1115 purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_accept\n" );
1116
1117 /* convert the packet to a byte stream */
1118 datalen = sprintf( data, "ms=" );
1119
1120 /* map chunk header over data buffer */
1121 chunk = (struct raw_chunk *) &data[datalen];
1122
1123 size = mxit_chunk_create_get( chunk->data, fileid, filesize, offset );
1124 if ( size < 0 ) {
1125 purple_debug_error( MXIT_PLUGIN_ID, "Error creating getfile chunk (%i)\n", size );
1126 return;
1127 }
1128
1129 chunk->type = CP_CHUNK_GET;
1130 chunk->length = htonl( size );
1131 datalen += sizeof( struct raw_chunk ) + size;
1132
1133 /* send the byte stream to the mxit server */
1134 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1135 }
1136
1137
1138 /*------------------------------------------------------------------------
1139 * Send a "received file" multimedia packet.
1140 *
1141 * @param session The MXit session object
1142 * @param status The status of the file-transfer
1143 */
1144 void mxit_send_file_received( struct MXitSession* session, const char* fileid, short status )
1145 {
1146 char data[CP_MAX_PACKET];
1147 int datalen = 0;
1148 struct raw_chunk* chunk;
1149 int size;
1150
1151 purple_debug_info( MXIT_PLUGIN_ID, "mxit_send_file_received\n" );
1152
1153 /* convert the packet to a byte stream */
1154 datalen = sprintf( data, "ms=" );
1155
1156 /* map chunk header over data buffer */
1157 chunk = (struct raw_chunk *) &data[datalen];
1158
1159 size = mxit_chunk_create_received( chunk->data, fileid, status );
1160 if ( size < 0 ) {
1161 purple_debug_error( MXIT_PLUGIN_ID, "Error creating received chunk (%i)\n", size );
1162 return;
1163 }
1164
1165 chunk->type = CP_CHUNK_RECIEVED;
1166 chunk->length = htonl( size );
1167 datalen += sizeof( struct raw_chunk ) + size;
1168
1169 /* send the byte stream to the mxit server */
1170 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1171 }
1172
1173
1174 /*------------------------------------------------------------------------
1175 * Send a "set avatar" multimedia packet.
1176 *
1177 * @param session The MXit session object
1178 * @param data The avatar data
1179 * @param buflen The length of the avatar data
1180 */
1181 void mxit_set_avatar( struct MXitSession* session, const unsigned char* avatar, int avatarlen )
1182 {
1183 char data[CP_MAX_PACKET];
1184 int datalen = 0;
1185 struct raw_chunk* chunk;
1186 int size;
1187
1188 purple_debug_info( MXIT_PLUGIN_ID, "mxit_set_avatar: %i bytes\n", avatarlen );
1189
1190 /* convert the packet to a byte stream */
1191 datalen = sprintf( data, "ms=" );
1192
1193 /* map chunk header over data buffer */
1194 chunk = (struct raw_chunk *) &data[datalen];
1195
1196 size = mxit_chunk_create_set_avatar( chunk->data, avatar, avatarlen );
1197 if ( size < 0 ) {
1198 purple_debug_error( MXIT_PLUGIN_ID, "Error creating set avatar chunk (%i)\n", size );
1199 return;
1200 }
1201
1202 chunk->type = CP_CHUNK_SET_AVATAR;
1203 chunk->length = htonl( size );
1204 datalen += sizeof( struct raw_chunk ) + size;
1205
1206 /* send the byte stream to the mxit server */
1207 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1208 }
1209
1210
1211 /*------------------------------------------------------------------------
1212 * Send a "get avatar" multimedia packet.
1213 *
1214 * @param session The MXit session object
1215 * @param mxitId The username who's avatar to request
1216 * @param avatarId The id of the avatar image (as string)
1217 * @param data The avatar data
1218 * @param buflen The length of the avatar data
1219 */
1220 void mxit_get_avatar( struct MXitSession* session, const char* mxitId, const char* avatarId )
1221 {
1222 char data[CP_MAX_PACKET];
1223 int datalen = 0;
1224 struct raw_chunk* chunk;
1225 int size;
1226
1227 purple_debug_info( MXIT_PLUGIN_ID, "mxit_get_avatar: %s\n", mxitId );
1228
1229 /* convert the packet to a byte stream */
1230 datalen = sprintf( data, "ms=" );
1231
1232 /* map chunk header over data buffer */
1233 chunk = (struct raw_chunk *) &data[datalen];
1234
1235 size = mxit_chunk_create_get_avatar( chunk->data, mxitId, avatarId, MXIT_AVATAR_SIZE );
1236 if ( size < 0 ) {
1237 purple_debug_error( MXIT_PLUGIN_ID, "Error creating get avatar chunk (%i)\n", size );
1238 return;
1239 }
1240
1241 chunk->type = CP_CHUNK_GET_AVATAR;
1242 chunk->length = htonl( size );
1243 datalen += sizeof( struct raw_chunk ) + size;
1244
1245 /* send the byte stream to the mxit server */
1246 mxit_queue_packet( session, data, datalen, CP_CMD_MEDIA );
1247 }
1248
1249
1250 /*------------------------------------------------------------------------
1251 * Process a login message packet.
1252 *
1253 * @param session The MXit session object
1254 * @param records The packet's data records
1255 * @param rcount The number of data records
1256 */
1257 static void mxit_parse_cmd_login( struct MXitSession* session, struct record** records, int rcount )
1258 {
1259 PurpleStatus* status;
1260 int presence;
1261 const char* profilelist[] = { CP_PROFILE_BIRTHDATE, CP_PROFILE_GENDER, CP_PROFILE_HIDENUMBER, CP_PROFILE_FULLNAME,
1262 CP_PROFILE_TITLE, CP_PROFILE_FIRSTNAME, CP_PROFILE_LASTNAME, CP_PROFILE_EMAIL,
1263 CP_PROFILE_MOBILENR };
1264
1265 purple_account_set_int( session->acc, MXIT_CONFIG_STATE, MXIT_STATE_LOGIN );
1266
1267 /* we were not yet logged in so we need to complete the login sequence here */
1268 session->flags |= MXIT_FLAG_LOGGEDIN;
1269 purple_connection_update_progress( session->con, _( "Successfully Logged In..." ), 3, 4 );
1270 purple_connection_set_state( session->con, PURPLE_CONNECTED );
1271
1272 /* display the current splash-screen */
1273 if ( splash_popup_enabled( session ) )
1274 splash_display( session );
1275
1276 /* update presence status */
1277 status = purple_account_get_active_status( session->acc );
1278 presence = mxit_convert_presence( purple_status_get_id( status ) );
1279 if ( presence != MXIT_PRESENCE_ONLINE ) {
1280 /* when logging into MXit, your default presence is online. but with the UI, one can change
1281 * the presence to whatever. in the case where its changed to a different presence setting
1282 * we need to send an update to the server, otherwise the user's presence will be out of
1283 * sync between the UI and MXit.
1284 */
1285 mxit_send_presence( session, presence, purple_status_get_attr_string( status, "message" ) );
1286 }
1287
1288 /* save extra info if this is a HTTP connection */
1289 if ( session->http ) {
1290 /* save the http server to use for this session */
1291 g_strlcpy( session->http_server, records[1]->fields[3]->data, sizeof( session->http_server ) );
1292
1293 /* save the session id */
1294 session->http_sesid = atoi( records[0]->fields[0]->data );
1295 }
1296
1297 /* retrieve our MXit profile */
1298 mxit_send_extprofile_request( session, NULL, ARRAY_SIZE( profilelist ), profilelist );
1299 }
1300
1301
1302 /*------------------------------------------------------------------------
1303 * Process a received message packet.
1304 *
1305 * @param session The MXit session object
1306 * @param records The packet's data records
1307 * @param rcount The number of data records
1308 */
1309 static void mxit_parse_cmd_message( struct MXitSession* session, struct record** records, int rcount )
1310 {
1311 struct RXMsgData* mx = NULL;
1312 char* message = NULL;
1313 int msglen = 0;
1314 int msgflags = 0;
1315 int msgtype = 0;
1316
1317 if ( ( rcount == 1 ) || ( records[0]->fcount < 2 ) || ( records[1]->fcount == 0 ) || ( records[1]->fields[0]->len == 0 ) ) {
1318 /* packet contains no message or an empty message */
1319 return;
1320 }
1321
1322 message = records[1]->fields[0]->data;
1323 msglen = strlen( message );
1324
1325 /* strip off dummy domain */
1326 mxit_strip_domain( records[0]->fields[0]->data );
1327
1328 #ifdef DEBUG_PROTOCOL
1329 purple_debug_info( MXIT_PLUGIN_ID, "Message received from '%s'\n", records[0]->fields[0]->data );
1330 #endif
1331
1332 /* decode message flags (if any) */
1333 if ( records[0]->fcount >= 5 )
1334 msgflags = atoi( records[0]->fields[4]->data );
1335 msgtype = atoi( records[0]->fields[2]->data );
1336
1337 if ( msgflags & CP_MSG_ENCRYPTED ) {
1338 /* this is an encrypted message. we do not currently support those so ignore it */
1339 PurpleBuddy* buddy;
1340 const char* name;
1341 char msg[128];
1342
1343 buddy = purple_find_buddy( session->acc, records[0]->fields[0]->data );
1344 if ( buddy )
1345 name = purple_buddy_get_alias( buddy );
1346 else
1347 name = records[0]->fields[0]->data;
1348 g_snprintf( msg, sizeof( msg ), "%s sent you an encrypted message, but it is not supported on this client.", name );
1349 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( msg ) );
1350 return;
1351 }
1352
1353 /* create and initialise new markup struct */
1354 mx = g_new0( struct RXMsgData, 1 );
1355 mx->msg = g_string_sized_new( msglen );
1356 mx->session = session;
1357 mx->from = g_strdup( records[0]->fields[0]->data );
1358 mx->timestamp = atoi( records[0]->fields[1]->data );
1359 mx->got_img = FALSE;
1360 mx->chatid = -1;
1361 mx->img_count = 0;
1362
1363 /* update list of active chats */
1364 if ( !find_active_chat( session->active_chats, mx->from ) ) {
1365 session->active_chats = g_list_append( session->active_chats, g_strdup( mx->from ) );
1366 }
1367
1368 if ( is_multimx_contact( session, mx->from ) ) {
1369 /* this is a MultiMx chatroom message */
1370 multimx_message_received( mx, message, msglen, msgtype, msgflags );
1371 }
1372 else {
1373 mxit_parse_markup( mx, message, msglen, msgtype, msgflags );
1374 }
1375
1376 /* we are now done parsing the message */
1377 mx->converted = TRUE;
1378 if ( mx->img_count == 0 ) {
1379 /* we have all the data we need for this message to be displayed now. */
1380 mxit_show_message( mx );
1381 }
1382 else {
1383 /* this means there are still images outstanding for this message and
1384 * still need to wait for them before we can display the message.
1385 * so the image received callback function will eventually display
1386 * the message. */
1387 }
1388 }
1389
1390
1391 /*------------------------------------------------------------------------
1392 * Process a received subscription request packet.
1393 *
1394 * @param session The MXit session object
1395 * @param records The packet's data records
1396 * @param rcount The number of data records
1397 */
1398 static void mxit_parse_cmd_new_sub( struct MXitSession* session, struct record** records, int rcount )
1399 {
1400 struct contact* contact;
1401 struct record* rec;
1402 int i;
1403
1404 purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_new_sub (%i recs)\n", rcount );
1405
1406 for ( i = 0; i < rcount; i++ ) {
1407 rec = records[i];
1408
1409 if ( rec->fcount < 4 ) {
1410 purple_debug_error( MXIT_PLUGIN_ID, "BAD SUBSCRIPTION RECORD! %i fields\n", rec->fcount );
1411 break;
1412 }
1413
1414 /* build up a new contact info struct */
1415 contact = g_new0( struct contact, 1 );
1416
1417 strcpy( contact->username, rec->fields[0]->data );
1418 mxit_strip_domain( contact->username ); /* remove dummy domain */
1419 strcpy( contact->alias, rec->fields[1]->data );
1420 contact->type = atoi( rec->fields[2]->data );
1421
1422 if ( rec->fcount >= 5 ) {
1423 /* there is a personal invite message attached */
1424 contact->msg = strdup( rec->fields[4]->data );
1425 }
1426 else
1427 contact->msg = NULL;
1428
1429 /* handle the subscription */
1430 if ( contact-> type == MXIT_TYPE_MULTIMX ) { /* subscription to a MultiMX room */
1431 char* creator = NULL;
1432
1433 if ( rec->fcount >= 6 )
1434 creator = rec->fields[5]->data;
1435
1436 multimx_invite( session, contact, creator );
1437 }
1438 else
1439 mxit_new_subscription( session, contact );
1440 }
1441 }
1442
1443
1444 /*------------------------------------------------------------------------
1445 * Process a received contact update packet.
1446 *
1447 * @param session The MXit session object
1448 * @param records The packet's data records
1449 * @param rcount The number of data records
1450 */
1451 static void mxit_parse_cmd_contact( struct MXitSession* session, struct record** records, int rcount )
1452 {
1453 struct contact* contact = NULL;
1454 struct record* rec;
1455 int i;
1456
1457 purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_contact (%i recs)\n", rcount );
1458
1459 for ( i = 0; i < rcount; i++ ) {
1460 rec = records[i];
1461
1462 if ( rec->fcount < 6 ) {
1463 purple_debug_error( MXIT_PLUGIN_ID, "BAD CONTACT RECORD! %i fields\n", rec->fcount );
1464 break;
1465 }
1466
1467 /* build up a new contact info struct */
1468 contact = g_new0( struct contact, 1 );
1469
1470 strcpy( contact->groupname, rec->fields[0]->data );
1471 strcpy( contact->username, rec->fields[1]->data );
1472 mxit_strip_domain( contact->username ); /* remove dummy domain */
1473 strcpy( contact->alias, rec->fields[2]->data );
1474
1475 contact->presence = atoi( rec->fields[3]->data );
1476 contact->type = atoi( rec->fields[4]->data );
1477 contact->mood = atoi( rec->fields[5]->data );
1478
1479 if ( rec->fcount > 6 ) {
1480 /* added in protocol 5.9.0 - flags & subtype */
1481 contact->flags = atoi( rec->fields[6]->data );
1482 contact->subtype = rec->fields[7]->data[0];
1483 }
1484
1485 /* add the contact to the buddy list */
1486 if ( contact-> type == MXIT_TYPE_MULTIMX ) /* contact is a MultiMX room */
1487 multimx_created( session, contact );
1488 else
1489 mxit_update_contact( session, contact );
1490 }
1491
1492 if ( !( session->flags & MXIT_FLAG_FIRSTROSTER ) ) {
1493 session->flags |= MXIT_FLAG_FIRSTROSTER;
1494 mxit_update_blist( session );
1495 }
1496 }
1497
1498
1499 /*------------------------------------------------------------------------
1500 * Process a received presence update packet.
1501 *
1502 * @param session The MXit session object
1503 * @param records The packet's data records
1504 * @param rcount The number of data records
1505 */
1506 static void mxit_parse_cmd_presence( struct MXitSession* session, struct record** records, int rcount )
1507 {
1508 struct record* rec;
1509 int i;
1510
1511 purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_presence (%i recs)\n", rcount );
1512
1513 for ( i = 0; i < rcount; i++ ) {
1514 rec = records[i];
1515
1516 if ( rec->fcount < 6 ) {
1517 purple_debug_error( MXIT_PLUGIN_ID, "BAD PRESENCE RECORD! %i fields\n", rec->fcount );
1518 break;
1519 }
1520
1521 /*
1522 * The format of the record is:
1523 * contactAddressN\1presenceN\1\moodN\1customMoodN\1statusMsgN\1avatarIdN
1524 */
1525 mxit_strip_domain( rec->fields[0]->data ); /* contactAddress */
1526
1527 mxit_update_buddy_presence( session, rec->fields[0]->data, atoi( rec->fields[1]->data ), atoi( rec->fields[2]->data ),
1528 rec->fields[3]->data, rec->fields[4]->data, rec->fields[5]->data );
1529 }
1530 }
1531
1532
1533 /*------------------------------------------------------------------------
1534 * Process a received extended profile packet.
1535 *
1536 * @param session The MXit session object
1537 * @param records The packet's data records
1538 * @param rcount The number of data records
1539 */
1540 static void mxit_parse_cmd_extprofile( struct MXitSession* session, struct record** records, int rcount )
1541 {
1542 const char* mxitId = records[0]->fields[0]->data;
1543 struct MXitProfile* profile = NULL;
1544 int count;
1545 int i;
1546
1547 purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_extprofile: profile for '%s'\n", mxitId );
1548
1549 profile = g_new0( struct MXitProfile, 1 );
1550
1551 /* set the count for attributes */
1552 count = atoi( records[0]->fields[1]->data );
1553
1554 for ( i = 0; i < count; i++ ) {
1555 char* fname;
1556 char* fvalue;
1557 char* fstatus;
1558 int f = ( i * 3 ) + 2;
1559
1560 fname = records[0]->fields[f]->data; /* field name */
1561 fvalue = records[0]->fields[f + 1]->data; /* field value */
1562 fstatus = records[0]->fields[f + 2]->data; /* field status */
1563
1564 /* first check the status on the returned attribute */
1565 if ( fstatus[0] != '0' ) {
1566 /* error: attribute requested was NOT found */
1567 purple_debug_error( MXIT_PLUGIN_ID, "Bad profile status on attribute '%s' \n", fname );
1568 continue;
1569 }
1570
1571 if ( strcmp( CP_PROFILE_BIRTHDATE, fname ) == 0 ) {
1572 /* birthdate */
1573 if ( records[0]->fields[f + 1]->len > 10 ) {
1574 fvalue[10] = '\0';
1575 records[0]->fields[f + 1]->len = 10;
1576 }
1577 memcpy( profile->birthday, fvalue, records[0]->fields[f + 1]->len );
1578 }
1579 else if ( strcmp( CP_PROFILE_GENDER, fname ) == 0 ) {
1580 /* gender */
1581 profile->male = ( fvalue[0] == '1' );
1582 }
1583 else if ( strcmp( CP_PROFILE_HIDENUMBER, fname ) == 0 ) {
1584 /* hide number */
1585 profile->hidden = ( fvalue[0] == '1' );
1586 }
1587 else if ( strcmp( CP_PROFILE_FULLNAME, fname ) == 0 ) {
1588 /* nickname */
1589 g_strlcpy( profile->nickname, fvalue, sizeof( profile->nickname ) );
1590 }
1591 else if ( strcmp( CP_PROFILE_AVATAR, fname ) == 0 ) {
1592 /* avatar id, we just ingore it cause we dont need it */
1593 }
1594 else if ( strcmp( CP_PROFILE_TITLE, fname ) == 0 ) {
1595 /* title */
1596 g_strlcpy( profile->title, fvalue, sizeof( profile->title ) );
1597 }
1598 else if ( strcmp( CP_PROFILE_FIRSTNAME, fname ) == 0 ) {
1599 /* first name */
1600 g_strlcpy( profile->firstname, fvalue, sizeof( profile->firstname ) );
1601 }
1602 else if ( strcmp( CP_PROFILE_LASTNAME, fname ) == 0 ) {
1603 /* last name */
1604 g_strlcpy( profile->lastname, fvalue, sizeof( profile->lastname ) );
1605 }
1606 else if ( strcmp( CP_PROFILE_EMAIL, fname ) == 0 ) {
1607 /* email address */
1608 g_strlcpy( profile->email, fvalue, sizeof( profile->email ) );
1609 }
1610 else if ( strcmp( CP_PROFILE_MOBILENR, fname ) == 0 ) {
1611 /* mobile number */
1612 g_strlcpy( profile->mobilenr, fvalue, sizeof( profile->mobilenr ) );
1613 }
1614 else {
1615 /* invalid profile attribute */
1616 purple_debug_error( MXIT_PLUGIN_ID, "Invalid profile attribute received '%s' \n", fname );
1617 }
1618 }
1619
1620 if ( records[0]->fields[0]->len == 0 ) {
1621 /* no MXit id provided, so this must be our own profile information */
1622 if ( session->profile )
1623 g_free( session->profile );
1624 session->profile = profile;
1625 }
1626 else {
1627 /* display other user's profile */
1628 mxit_show_profile( session, mxitId, profile );
1629
1630 /* cleanup */
1631 g_free( profile );
1632 }
1633 }
1634
1635
1636 /*------------------------------------------------------------------------
1637 * Return the length of a multimedia chunk
1638 *
1639 * @return The actual chunk data length in bytes
1640 */
1641 static int get_chunk_len( const char* chunkdata )
1642 {
1643 int* sizeptr;
1644
1645 sizeptr = (int*) &chunkdata[1]; /* we skip the first byte (type field) */
1646
1647 return ntohl( *sizeptr );
1648 }
1649
1650
1651 /*------------------------------------------------------------------------
1652 * Process a received multimedia packet.
1653 *
1654 * @param session The MXit session object
1655 * @param records The packet's data records
1656 * @param rcount The number of data records
1657 */
1658 static void mxit_parse_cmd_media( struct MXitSession* session, struct record** records, int rcount )
1659 {
1660 char type;
1661 int size;
1662
1663 type = records[0]->fields[0]->data[0];
1664 size = get_chunk_len( records[0]->fields[0]->data );
1665
1666 purple_debug_info( MXIT_PLUGIN_ID, "mxit_parse_cmd_media (%i records) (%i bytes)\n", rcount, size );
1667
1668 /* supported chunked data types */
1669 switch ( type ) {
1670 case CP_CHUNK_CUSTOM : /* custom resource */
1671 {
1672 struct cr_chunk chunk;
1673
1674 /* decode the chunked data */
1675 memset( &chunk, 0, sizeof( struct cr_chunk ) );
1676 mxit_chunk_parse_cr( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
1677
1678 purple_debug_info( MXIT_PLUGIN_ID, "chunk info id=%s handle=%s op=%i\n", chunk.id, chunk.handle, chunk.operation );
1679
1680 /* this is a splash-screen operation */
1681 if ( strcmp( chunk.handle, HANDLE_SPLASH2 ) == 0 ) {
1682 if ( chunk.operation == CR_OP_UPDATE ) { /* update the splash-screen */
1683 struct splash_chunk *splash = chunk.resources->data; // TODO: Fix - assuming 1st resource is splash
1684 gboolean clickable = ( g_list_length( chunk.resources ) > 1 ); // TODO: Fix - if 2 resources, then is clickable
1685
1686 if ( splash != NULL )
1687 splash_update( session, chunk.id, splash->data, splash->datalen, clickable );
1688 }
1689 else if ( chunk.operation == CR_OP_REMOVE ) /* remove the splash-screen */
1690 splash_remove( session );
1691 }
1692
1693 /* cleanup custom resources */
1694 g_list_foreach( chunk.resources, (GFunc)g_free, NULL );
1695
1696 }
1697 break;
1698
1699 case CP_CHUNK_OFFER : /* file offer */
1700 {
1701 struct offerfile_chunk chunk;
1702
1703 /* decode the chunked data */
1704 memset( &chunk, 0, sizeof( struct offerfile_chunk ) );
1705 mxit_chunk_parse_offer( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
1706
1707 /* process the offer */
1708 mxit_xfer_rx_offer( session, chunk.username, chunk.filename, chunk.filesize, chunk.fileid );
1709 }
1710 break;
1711
1712 case CP_CHUNK_GET : /* get file response */
1713 {
1714 struct getfile_chunk chunk;
1715
1716 /* decode the chunked data */
1717 memset( &chunk, 0, sizeof( struct getfile_chunk ) );
1718 mxit_chunk_parse_get( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
1719
1720 /* process the getfile */
1721 mxit_xfer_rx_file( session, chunk.fileid, chunk.data, chunk.length );
1722 }
1723 break;
1724
1725 case CP_CHUNK_GET_AVATAR : /* get avatars */
1726 {
1727 struct getavatar_chunk chunk;
1728
1729 /* decode the chunked data */
1730 memset( &chunk, 0, sizeof ( struct getavatar_chunk ) );
1731 mxit_chunk_parse_get_avatar( &records[0]->fields[0]->data[sizeof( char ) + sizeof( int )], records[0]->fields[0]->len, &chunk );
1732
1733 /* update avatar image */
1734 if ( chunk.data ) {
1735 purple_debug_info( MXIT_PLUGIN_ID, "updating avatar for contact '%s'\n", chunk.mxitid );
1736 purple_buddy_icons_set_for_user( session->acc, chunk.mxitid, g_memdup( chunk.data, chunk.length), chunk.length, chunk.avatarid );
1737 }
1738
1739 }
1740 break;
1741
1742 case CP_CHUNK_SET_AVATAR :
1743 /* this is a reply packet to a set avatar request. no action is required */
1744 break;
1745
1746 case CP_CHUNK_DIRECT_SND :
1747 /* this is a ack for a file send. no action is required */
1748 break;
1749
1750 case CP_CHUNK_RECIEVED :
1751 /* this is a ack for a file received. no action is required */
1752 break;
1753
1754 default :
1755 purple_debug_error( MXIT_PLUGIN_ID, "Unsupported chunked data packet type received (%i)\n", type );
1756 break;
1757 }
1758 }
1759
1760
1761 /*------------------------------------------------------------------------
1762 * Handle a redirect sent from the MXit server.
1763 *
1764 * @param session The MXit session object
1765 * @param url The redirect information
1766 */
1767 static void mxit_perform_redirect( struct MXitSession* session, const char* url )
1768 {
1769 gchar** parts;
1770 gchar** host;
1771 int type;
1772
1773 purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s\n", url );
1774
1775 /* tokenize the URL string */
1776 parts = g_strsplit( url, ";", 0 );
1777
1778 /* Part 1: protocol://host:port */
1779 host = g_strsplit( parts[0], ":", 4 );
1780 if ( strcmp( host[0], "socket" ) == 0 ) {
1781 /* redirect to a MXit socket proxy */
1782 g_strlcpy( session->server, &host[1][2], sizeof( session->server ) );
1783 session->port = atoi( host[2] );
1784 }
1785 else {
1786 purple_connection_error( session->con, _( "Cannot perform redirect using the specified protocol" ) );
1787 goto redirect_fail;
1788 }
1789
1790 /* Part 2: type of redirect */
1791 type = atoi( parts[1] );
1792 if ( type == CP_REDIRECT_PERMANENT ) {
1793 /* permanent redirect, so save new MXit server and port */
1794 purple_account_set_string( session->acc, MXIT_CONFIG_SERVER_ADDR, session->server );
1795 purple_account_set_int( session->acc, MXIT_CONFIG_SERVER_PORT, session->port );
1796 }
1797
1798 /* Part 3: message (optional) */
1799 if ( parts[2] != NULL )
1800 purple_connection_notice( session->con, parts[2] );
1801
1802 purple_debug_info( MXIT_PLUGIN_ID, "mxit_perform_redirect: %s redirect to %s:%i\n",
1803 ( type == CP_REDIRECT_PERMANENT ) ? "Permanent" : "Temporary", session->server, session->port );
1804
1805 /* perform the re-connect to the new MXit server */
1806 mxit_reconnect( session );
1807
1808 redirect_fail:
1809 g_strfreev( parts );
1810 g_strfreev( host );
1811 }
1812
1813
1814 /*------------------------------------------------------------------------
1815 * Process a success response received from the MXit server.
1816 *
1817 * @param session The MXit session object
1818 * @param packet The received packet
1819 */
1820 static int process_success_response( struct MXitSession* session, struct rx_packet* packet )
1821 {
1822 /* ignore ping/poll packets */
1823 if ( ( packet->cmd != CP_CMD_PING ) && ( packet->cmd != CP_CMD_POLL ) )
1824 session->last_rx = time( NULL );
1825
1826 /*
1827 * when we pass the packet records to the next level for parsing
1828 * we minus 3 records because 1) the first record is the packet
1829 * type 2) packet reply status 3) the last record is bogus
1830 */
1831
1832 /* packet command */
1833 switch ( packet->cmd ) {
1834
1835 case CP_CMD_REGISTER :
1836 /* fall through, when registeration successful, MXit will auto login */
1837 case CP_CMD_LOGIN :
1838 /* login response */
1839 if ( !( session->flags & MXIT_FLAG_LOGGEDIN ) ) {
1840 mxit_parse_cmd_login( session, &packet->records[2], packet->rcount - 3 );
1841 }
1842 break;
1843
1844 case CP_CMD_LOGOUT :
1845 /* logout response */
1846 session->flags &= ~MXIT_FLAG_LOGGEDIN;
1847 purple_account_disconnect( session->acc );
1848
1849 /* note:
1850 * we do not prompt the user here for a reconnect, because this could be the user
1851 * logging in with his phone. so we just disconnect the account otherwise
1852 * mxit will start to bounce between the phone and pidgin. also could be a valid
1853 * disconnect selected by the user.
1854 */
1855 return -1;
1856
1857 case CP_CMD_CONTACT :
1858 /* contact update */
1859 mxit_parse_cmd_contact( session, &packet->records[2], packet->rcount - 3 );
1860 break;
1861
1862 case CP_CMD_PRESENCE :
1863 /* presence update */
1864 mxit_parse_cmd_presence(session, &packet->records[2], packet->rcount - 3 );
1865 break;
1866
1867 case CP_CMD_RX_MSG :
1868 /* incoming message (no bogus record) */
1869 mxit_parse_cmd_message( session, &packet->records[2], packet->rcount - 2 );
1870 break;
1871
1872 case CP_CMD_NEW_SUB :
1873 /* new subscription request */
1874 mxit_parse_cmd_new_sub( session, &packet->records[2], packet->rcount - 3 );
1875 break;
1876
1877 case CP_CMD_MEDIA :
1878 /* multi-media message */
1879 mxit_parse_cmd_media( session, &packet->records[2], packet->rcount - 2 );
1880 break;
1881
1882 case CP_CMD_EXTPROFILE_GET :
1883 /* profile update */
1884 mxit_parse_cmd_extprofile( session, &packet->records[2], packet->rcount - 2 );
1885 break;
1886
1887 case CP_CMD_MOOD :
1888 /* mood update */
1889 case CP_CMD_UPDATE :
1890 /* update contact information */
1891 case CP_CMD_ALLOW :
1892 /* allow subscription ack */
1893 case CP_CMD_DENY :
1894 /* deny subscription ack */
1895 case CP_CMD_INVITE :
1896 /* invite contact ack */
1897 case CP_CMD_REMOVE :
1898 /* remove contact ack */
1899 case CP_CMD_TX_MSG :
1900 /* outgoing message ack */
1901 case CP_CMD_STATUS :
1902 /* presence update ack */
1903 case CP_CMD_GRPCHAT_CREATE :
1904 /* create groupchat */
1905 case CP_CMD_GRPCHAT_INVITE :
1906 /* groupchat invite */
1907 case CP_CMD_PING :
1908 /* ping reply */
1909 case CP_CMD_POLL :
1910 /* HTTP poll reply */
1911 case CP_CMD_EXTPROFILE_SET :
1912 /* profile update */
1913 case CP_CMD_SPLASHCLICK :
1914 /* splash-screen clickthrough */
1915 break;
1916
1917 default :
1918 /* unknown packet */
1919 purple_debug_error( MXIT_PLUGIN_ID, "Received unknown client packet (cmd = %i)\n", packet->cmd );
1920 }
1921
1922 return 0;
1923 }
1924
1925
1926 /*------------------------------------------------------------------------
1927 * Process an error response received from the MXit server.
1928 *
1929 * @param session The MXit session object
1930 * @param packet The received packet
1931 */
1932 static int process_error_response( struct MXitSession* session, struct rx_packet* packet )
1933 {
1934 char errmsg[256];
1935 const char* errdesc;
1936
1937 /* set the error description to be shown to the user */
1938 if ( packet->errmsg )
1939 errdesc = packet->errmsg;
1940 else
1941 errdesc = "An internal MXit server error occurred.";
1942
1943 purple_debug_info( MXIT_PLUGIN_ID, "Error Reply %i:%s\n", packet->errcode, errdesc );
1944
1945 if ( packet->errcode == MXIT_ERRCODE_LOGGEDOUT ) {
1946 /* we are not currently logged in, so we need to reconnect */
1947 purple_connection_error( session->con, _( errmsg ) );
1948 }
1949
1950 /* packet command */
1951 switch ( packet->cmd ) {
1952
1953 case CP_CMD_REGISTER :
1954 case CP_CMD_LOGIN :
1955 if ( packet->errcode == MXIT_ERRCODE_REDIRECT ) {
1956 mxit_perform_redirect( session, packet->errmsg );
1957 return 0;
1958 }
1959 else {
1960 sprintf( errmsg, "Login error: %s (%i)", errdesc, packet->errcode );
1961 purple_connection_error( session->con, _( errmsg ) );
1962 return -1;
1963 }
1964 case CP_CMD_LOGOUT :
1965 sprintf( errmsg, "Logout error: %s (%i)", errdesc, packet->errcode );
1966 purple_connection_error_reason( session->con, PURPLE_CONNECTION_ERROR_NAME_IN_USE, _( errmsg ) );
1967 return -1;
1968 case CP_CMD_CONTACT :
1969 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Error" ), _( errdesc ) );
1970 break;
1971 case CP_CMD_RX_MSG :
1972 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Error" ), _( errdesc ) );
1973 break;
1974 case CP_CMD_TX_MSG :
1975 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Message Sending Error" ), _( errdesc ) );
1976 break;
1977 case CP_CMD_STATUS :
1978 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Status Error" ), _( errdesc ) );
1979 break;
1980 case CP_CMD_MOOD :
1981 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Mood Error" ), _( errdesc ) );
1982 break;
1983 case CP_CMD_KICK :
1984 /*
1985 * the MXit server sends this packet if we were idle for too long.
1986 * to stop the server from closing this connection we need to resend
1987 * the login packet.
1988 */
1989 mxit_send_login( session );
1990 break;
1991 case CP_CMD_INVITE :
1992 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Invitation Error" ), _( errdesc ) );
1993 break;
1994 case CP_CMD_REMOVE :
1995 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Removal Error" ), _( errdesc ) );
1996 break;
1997 case CP_CMD_ALLOW :
1998 case CP_CMD_DENY :
1999 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Subscription Error" ), _( errdesc ) );
2000 break;
2001 case CP_CMD_UPDATE :
2002 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Contact Update Error" ), _( errdesc ) );
2003 break;
2004 case CP_CMD_MEDIA :
2005 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "File Transfer Error" ), _( errdesc ) );
2006 break;
2007 case CP_CMD_GRPCHAT_CREATE :
2008 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Cannot create MultiMx room" ), _( errdesc ) );
2009 break;
2010 case CP_CMD_GRPCHAT_INVITE :
2011 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "MultiMx Invitation Error" ), _( errdesc ) );
2012 break;
2013 case CP_CMD_EXTPROFILE_GET :
2014 case CP_CMD_EXTPROFILE_SET :
2015 mxit_popup( PURPLE_NOTIFY_MSG_WARNING, _( "Profile Error" ), _( errdesc ) );
2016 break;
2017 case CP_CMD_SPLASHCLICK :
2018 /* ignore error */
2019 break;
2020 case CP_CMD_PING :
2021 case CP_CMD_POLL :
2022 break;
2023 default :
2024 mxit_popup( PURPLE_NOTIFY_MSG_ERROR, _( "Error" ), _( errdesc ) );
2025 break;
2026 }
2027
2028 return 0;
2029 }
2030
2031
2032 /*========================================================================================================================
2033 * Low-level Packet receive
2034 */
2035
2036 #ifdef DEBUG_PROTOCOL
2037 /*------------------------------------------------------------------------
2038 * Dump a received packet structure.
2039 *
2040 * @param p The received packet
2041 */
2042 static void dump_packet( struct rx_packet* p )
2043 {
2044 struct record* r = NULL;
2045 struct field* f = NULL;
2046 int i;
2047 int j;
2048
2049 purple_debug_info( MXIT_PLUGIN_ID, "PACKET DUMP: (%i records)\n", p->rcount );
2050
2051 for ( i = 0; i < p->rcount; i++ ) {
2052 r = p->records[i];
2053 purple_debug_info( MXIT_PLUGIN_ID, "RECORD: (%i fields)\n", r->fcount );
2054
2055 for ( j = 0; j < r->fcount; j++ ) {
2056 f = r->fields[j];
2057 purple_debug_info( MXIT_PLUGIN_ID, "\tFIELD: (len=%i) '%s' \n", f->len, f->data );
2058 }
2059 }
2060 }
2061 #endif
2062
2063
2064 /*------------------------------------------------------------------------
2065 * Free up memory used by a packet structure.
2066 *
2067 * @param p The received packet
2068 */
2069 static void free_rx_packet( struct rx_packet* p )
2070 {
2071 struct record* r = NULL;
2072 struct field* f = NULL;
2073 int i;
2074 int j;
2075
2076 for ( i = 0; i < p->rcount; i++ ) {
2077 r = p->records[i];
2078
2079 for ( j = 0; j < r->fcount; j++ ) {
2080 g_free( f );
2081 }
2082 g_free( r->fields );
2083 g_free( r );
2084 }
2085 g_free( p->records );
2086 }
2087
2088
2089 /*------------------------------------------------------------------------
2090 * Add a new field to a record.
2091 *
2092 * @param r Parent record object
2093 * @return The newly created field
2094 */
2095 static struct field* add_field( struct record* r )
2096 {
2097 struct field* field;
2098
2099 field = g_new0( struct field, 1 );
2100
2101 r->fields = realloc( r->fields, sizeof( struct field* ) * ( r->fcount + 1 ) );
2102 r->fields[r->fcount] = field;
2103 r->fcount++;
2104
2105 return field;
2106 }
2107
2108
2109 /*------------------------------------------------------------------------
2110 * Add a new record to a packet.
2111 *
2112 * @param p The packet object
2113 * @return The newly created record
2114 */
2115 static struct record* add_record( struct rx_packet* p )
2116 {
2117 struct record* rec;
2118
2119 rec = g_new0( struct record, 1 );
2120
2121 p->records = realloc( p->records, sizeof( struct record* ) * ( p->rcount + 1 ) );
2122 p->records[p->rcount] = rec;
2123 p->rcount++;
2124
2125 return rec;
2126 }
2127
2128
2129 /*------------------------------------------------------------------------
2130 * Parse the received byte stream into a proper client protocol packet.
2131 *
2132 * @param session The MXit session object
2133 * @return Success (0) or Failure (!0)
2134 */
2135 int mxit_parse_packet( struct MXitSession* session )
2136 {
2137 struct rx_packet packet;
2138 struct record* rec;
2139 struct field* field;
2140 gboolean pbreak;
2141 unsigned int i;
2142 int res = 0;
2143
2144 #ifdef DEBUG_PROTOCOL
2145 purple_debug_info( MXIT_PLUGIN_ID, "Received packet (%i bytes)\n", session->rx_i );
2146 dump_bytes( session, session->rx_dbuf, session->rx_i );
2147 #endif
2148
2149 i = 0;
2150 while ( i < session->rx_i ) {
2151
2152 /* create first record and field */
2153 rec = NULL;
2154 field = NULL;
2155 memset( &packet, 0x00, sizeof( struct rx_packet ) );
2156 rec = add_record( &packet );
2157 pbreak = FALSE;
2158
2159 /* break up the received packet into fields and records for easy parsing */
2160 while ( ( i < session->rx_i ) && ( !pbreak ) ) {
2161
2162 switch ( session->rx_dbuf[i] ) {
2163 case CP_SOCK_REC_TERM :
2164 /* new record */
2165 if ( packet.rcount == 1 ) {
2166 /* packet command */
2167 packet.cmd = atoi( packet.records[0]->fields[0]->data );
2168 }
2169 else if ( packet.rcount == 2 ) {
2170 /* special case: binary multimedia packets should not be parsed here */
2171 if ( packet.cmd == CP_CMD_MEDIA ) {
2172 /* add the chunked to new record */
2173 rec = add_record( &packet );
2174 field = add_field( rec );
2175 field->data = &session->rx_dbuf[i + 1];
2176 field->len = session->rx_i - i;
2177 /* now skip the binary data */
2178 res = get_chunk_len( field->data );
2179 /* determine if we have more packets */
2180 if ( res + 6 + i < session->rx_i ) {
2181 /* we have more than one packet in this stream */
2182 i += res + 6;
2183 pbreak = TRUE;
2184 }
2185 else {
2186 i = session->rx_i;
2187 }
2188 }
2189 }
2190 else if ( !field ) {
2191 field = add_field( rec );
2192 field->data = &session->rx_dbuf[i];
2193 }
2194 session->rx_dbuf[i] = '\0';
2195 rec = add_record( &packet );
2196 field = NULL;
2197
2198 break;
2199 case CP_FLD_TERM :
2200 /* new field */
2201 session->rx_dbuf[i] = '\0';
2202 if ( !field ) {
2203 field = add_field( rec );
2204 field->data = &session->rx_dbuf[i];
2205 }
2206 field = NULL;
2207 break;
2208 case CP_PKT_TERM :
2209 /* packet is done! */
2210 session->rx_dbuf[i] = '\0';
2211 pbreak = TRUE;
2212 break;
2213 default :
2214 /* skip non special characters */
2215 if ( !field ) {
2216 field = add_field( rec );
2217 field->data = &session->rx_dbuf[i];
2218 }
2219 field->len++;
2220 break;
2221 }
2222
2223 i++;
2224 }
2225
2226 if ( packet.rcount < 2 ) {
2227 /* bad packet */
2228 purple_connection_error( session->con, _( "Invalid packet received from MXit." ) );
2229 free_rx_packet( &packet );
2230 continue;
2231 }
2232
2233 session->rx_dbuf[session->rx_i] = '\0';
2234 packet.errcode = atoi( packet.records[1]->fields[0]->data );
2235
2236 purple_debug_info( MXIT_PLUGIN_ID, "Packet received CMD:%i (%i)\n", packet.cmd, packet.errcode );
2237 #ifdef DEBUG_PROTOCOL
2238 /* debug */
2239 dump_packet( &packet );
2240 #endif
2241
2242 /* reset the out ack */
2243 if ( session->outack == packet.cmd ) {
2244 /* outstanding ack received from mxit server */
2245 session->outack = 0;
2246 }
2247
2248 /* check packet status */
2249 if ( packet.errcode != MXIT_ERRCODE_SUCCESS ) {
2250 /* error reply! */
2251 if ( ( packet.records[1]->fcount > 1 ) && ( packet.records[1]->fields[1]->data ) )
2252 packet.errmsg = packet.records[1]->fields[1]->data;
2253 else
2254 packet.errmsg = NULL;
2255
2256 res = process_error_response( session, &packet );
2257 }
2258 else {
2259 /* success reply! */
2260 res = process_success_response( session, &packet );
2261 }
2262
2263 /* free up the packet resources */
2264 free_rx_packet( &packet );
2265 }
2266
2267 if ( session->outack == 0 )
2268 mxit_manage_queue( session );
2269
2270 return res;
2271 }
2272
2273
2274 /*------------------------------------------------------------------------
2275 * Callback when data is received from the MXit server.
2276 *
2277 * @param user_data The MXit session object
2278 * @param source The file-descriptor on which data was received
2279 * @param cond Condition which caused the callback (PURPLE_INPUT_READ)
2280 */
2281 void mxit_cb_rx( gpointer user_data, gint source, PurpleInputCondition cond )
2282 {
2283 struct MXitSession* session = (struct MXitSession*) user_data;
2284 char ch;
2285 int res;
2286 int len;
2287
2288 if ( session->rx_state == RX_STATE_RLEN ) {
2289 /* we are reading in the packet length */
2290 len = read( session->fd, &ch, 1 );
2291 if ( len < 0 ) {
2292 /* connection error */
2293 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x01)" ) );
2294 return;
2295 }
2296 else if ( len == 0 ) {
2297 /* connection closed */
2298 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x02)" ) );
2299 return;
2300 }
2301 else {
2302 /* byte read */
2303 if ( ch == CP_REC_TERM ) {
2304 /* the end of the length record found */
2305 session->rx_lbuf[session->rx_i] = '\0';
2306 session->rx_res = atoi( &session->rx_lbuf[3] );
2307 if ( session->rx_res > CP_MAX_PACKET ) {
2308 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x03)" ) );
2309 }
2310 session->rx_state = RX_STATE_DATA;
2311 session->rx_i = 0;
2312 }
2313 else {
2314 /* still part of the packet length record */
2315 session->rx_lbuf[session->rx_i] = ch;
2316 session->rx_i++;
2317 if ( session->rx_i >= sizeof( session->rx_lbuf ) ) {
2318 /* malformed packet length record (too long) */
2319 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x04)" ) );
2320 return;
2321 }
2322 }
2323 }
2324 }
2325 else if ( session->rx_state == RX_STATE_DATA ) {
2326 /* we are reading in the packet data */
2327 len = read( session->fd, &session->rx_dbuf[session->rx_i], session->rx_res );
2328 if ( len < 0 ) {
2329 /* connection error */
2330 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x05)" ) );
2331 return;
2332 }
2333 else if ( len == 0 ) {
2334 /* connection closed */
2335 purple_connection_error( session->con, _( "A connection error occurred to MXit. (read stage 0x06)" ) );
2336 return;
2337 }
2338 else {
2339 /* data read */
2340 session->rx_i += len;
2341 session->rx_res -= len;
2342
2343 if ( session->rx_res == 0 ) {
2344 /* ok, so now we have read in the whole packet */
2345 session->rx_state = RX_STATE_PROC;
2346 }
2347 }
2348 }
2349
2350 if ( session->rx_state == RX_STATE_PROC ) {
2351 /* we have a full packet, which we now need to process */
2352 res = mxit_parse_packet( session );
2353
2354 if ( res == 0 ) {
2355 /* we are still logged in */
2356 session->rx_state = RX_STATE_RLEN;
2357 session->rx_res = 0;
2358 session->rx_i = 0;
2359 }
2360 }
2361 }
2362
2363
2364 /*------------------------------------------------------------------------
2365 * Log the user off MXit and close the connection
2366 *
2367 * @param session The MXit session object
2368 */
2369 void mxit_close_connection( struct MXitSession* session )
2370 {
2371 purple_debug_info( MXIT_PLUGIN_ID, "mxit_close_connection\n" );
2372
2373 if ( !( session->flags & MXIT_FLAG_CONNECTED ) ) {
2374 /* we are already closed */
2375 return;
2376 }
2377 else if ( session->flags & MXIT_FLAG_LOGGEDIN ) {
2378 /* we are currently logged in so we need to send a logout packet */
2379 if ( !session->http ) {
2380 mxit_send_logout( session );
2381 }
2382 session->flags &= ~MXIT_FLAG_LOGGEDIN;
2383 }
2384 session->flags &= ~MXIT_FLAG_CONNECTED;
2385
2386 /* cancel outstanding HTTP request */
2387 if ( ( session->http ) && ( session->http_out_req ) ) {
2388 purple_util_fetch_url_cancel( (PurpleUtilFetchUrlData*) session->http_out_req );
2389 session->http_out_req = NULL;
2390 }
2391
2392 /* remove the input cb function */
2393 if ( session->con->inpa ) {
2394 purple_input_remove( session->con->inpa );
2395 session->con->inpa = 0;
2396 }
2397
2398 /* remove HTTP poll timer */
2399 if ( session->http_timer_id > 0 )
2400 purple_timeout_remove( session->http_timer_id );
2401
2402 /* remove queue manager timer */
2403 if ( session->q_timer > 0 )
2404 purple_timeout_remove( session->q_timer );
2405
2406 /* remove all groupchat rooms */
2407 while ( session->rooms != NULL ) {
2408 struct multimx* multimx = (struct multimx *) session->rooms->data;
2409
2410 session->rooms = g_list_remove( session->rooms, multimx );
2411
2412 free( multimx );
2413 }
2414 g_list_free( session->rooms );
2415 session->rooms = NULL;
2416
2417 /* remove all rx chats names */
2418 while ( session->active_chats != NULL ) {
2419 char* chat = (char*) session->active_chats->data;
2420
2421 session->active_chats = g_list_remove( session->active_chats, chat );
2422
2423 g_free( chat );
2424 }
2425 g_list_free( session->active_chats );
2426 session->active_chats = NULL;
2427
2428 /* free profile information */
2429 if ( session->profile )
2430 free( session->profile );
2431
2432 /* free custom emoticons */
2433 mxit_free_emoticon_cache( session );
2434
2435 /* free allocated memory */
2436 g_free( session->encpwd );
2437 session->encpwd = NULL;
2438
2439 /* flush all the commands still in the queue */
2440 flush_queue( session );
2441 }
2442