source: protocols/msn/sb.c @ ee6cc94

Last change on this file since ee6cc94 was bc090f0, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-03-20T22:42:59Z

Error reporting and added a msgq_send function. Need to put some more
intelligence into it later.

  • Property mode set to 100644
File size: 17.6 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2005 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* MSN module - Switchboard server callbacks and utilities              */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#include <ctype.h>
27#include "nogaim.h"
28#include "msn.h"
29#include "passport.h"
30#include "md5.h"
31#include "soap.h"
32
33static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond );
34static int msn_sb_command( gpointer data, char **cmd, int num_parts );
35static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts );
36
37int msn_sb_write( struct msn_switchboard *sb, char *s, int len )
38{
39        int st;
40       
41        st = write( sb->fd, s, len );
42        if( st != len )
43        {
44                msn_sb_destroy( sb );
45                return( 0 );
46        }
47       
48        return( 1 );
49}
50
51int msn_sb_write_msg( struct im_connection *ic, struct msn_message *m )
52{
53        struct msn_data *md = ic->proto_data;
54        struct msn_switchboard *sb;
55        char buf[1024];
56
57        /* FIXME: *CHECK* the reliability of using spare sb's! */
58        if( ( sb = msn_sb_spare( ic ) ) )
59        {
60                debug( "Trying to use a spare switchboard to message %s", m->who );
61               
62                sb->who = g_strdup( m->who );
63                g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, m->who );
64                if( msn_sb_write( sb, buf, strlen( buf ) ) )
65                {
66                        /* He/She should join the switchboard soon, let's queue the message. */
67                        sb->msgq = g_slist_append( sb->msgq, m );
68                        return( 1 );
69                }
70        }
71       
72        debug( "Creating a new switchboard to message %s", m->who );
73       
74        /* If we reach this line, there was no spare switchboard, so let's make one. */
75        g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
76        if( !msn_write( ic, buf, strlen( buf ) ) )
77        {
78                g_free( m->who );
79                g_free( m->text );
80                g_free( m );
81               
82                return( 0 );
83        }
84       
85        /* And queue the message to md. We'll pick it up when the switchboard comes up. */
86        md->msgq = g_slist_append( md->msgq, m );
87       
88        /* FIXME: If the switchboard creation fails, the message will not be sent. */
89       
90        return( 1 );
91}
92
93struct msn_switchboard *msn_sb_create( struct im_connection *ic, char *host, int port, char *key, int session )
94{
95        struct msn_data *md = ic->proto_data;
96        struct msn_switchboard *sb = g_new0( struct msn_switchboard, 1 );
97       
98        sb->fd = proxy_connect( host, port, msn_sb_connected, sb );
99        if( sb->fd < 0 )
100        {
101                g_free( sb );
102                return( NULL );
103        }
104       
105        sb->ic = ic;
106        sb->key = g_strdup( key );
107        sb->session = session;
108       
109        msn_switchboards = g_slist_append( msn_switchboards, sb );
110        md->switchboards = g_slist_append( md->switchboards, sb );
111       
112        return( sb );
113}
114
115struct msn_switchboard *msn_sb_by_handle( struct im_connection *ic, char *handle )
116{
117        struct msn_data *md = ic->proto_data;
118        struct msn_switchboard *sb;
119        GSList *l;
120       
121        for( l = md->switchboards; l; l = l->next )
122        {
123                sb = l->data;
124                if( sb->who && strcmp( sb->who, handle ) == 0 )
125                        return( sb );
126        }
127       
128        return( NULL );
129}
130
131struct msn_switchboard *msn_sb_by_chat( struct groupchat *c )
132{
133        struct msn_data *md = c->ic->proto_data;
134        struct msn_switchboard *sb;
135        GSList *l;
136       
137        for( l = md->switchboards; l; l = l->next )
138        {
139                sb = l->data;
140                if( sb->chat == c )
141                        return( sb );
142        }
143       
144        return( NULL );
145}
146
147struct msn_switchboard *msn_sb_spare( struct im_connection *ic )
148{
149        struct msn_data *md = ic->proto_data;
150        struct msn_switchboard *sb;
151        GSList *l;
152       
153        for( l = md->switchboards; l; l = l->next )
154        {
155                sb = l->data;
156                if( !sb->who && !sb->chat )
157                        return( sb );
158        }
159       
160        return( NULL );
161}
162
163int msn_sb_sendmessage( struct msn_switchboard *sb, char *text )
164{
165        if( sb->ready )
166        {
167                char *packet, *buf;
168                int i, j;
169               
170                /* Build the message. Convert LF to CR-LF for normal messages. */
171                if( strcmp( text, TYPING_NOTIFICATION_MESSAGE ) != 0 )
172                {
173                        buf = g_new0( char, sizeof( MSN_MESSAGE_HEADERS ) + strlen( text ) * 2 + 1 );
174                        i = strlen( MSN_MESSAGE_HEADERS );
175                       
176                        strcpy( buf, MSN_MESSAGE_HEADERS );
177                        for( j = 0; text[j]; j ++ )
178                        {
179                                if( text[j] == '\n' )
180                                        buf[i++] = '\r';
181                               
182                                buf[i++] = text[j];
183                        }
184                }
185                else
186                {
187                        i = strlen( MSN_TYPING_HEADERS ) + strlen( sb->ic->acc->user );
188                        buf = g_new0( char, i );
189                        i = g_snprintf( buf, i, MSN_TYPING_HEADERS, sb->ic->acc->user );
190                }
191               
192                /* Build the final packet (MSG command + the message). */
193                packet = g_strdup_printf( "MSG %d N %d\r\n%s", ++sb->trId, i, buf );
194                g_free( buf );
195                if( msn_sb_write( sb, packet, strlen( packet ) ) )
196                {
197                        g_free( packet );
198                        return( 1 );
199                }
200                else
201                {
202                        g_free( packet );
203                        return( 0 );
204                }
205        }
206        else if( sb->who )
207        {
208                struct msn_message *m = g_new0( struct msn_message, 1 );
209               
210                m->who = g_strdup( "" );
211                m->text = g_strdup( text );
212                sb->msgq = g_slist_append( sb->msgq, m );
213               
214                return( 1 );
215        }
216        else
217        {
218                return( 0 );
219        }
220}
221
222struct groupchat *msn_sb_to_chat( struct msn_switchboard *sb )
223{
224        struct im_connection *ic = sb->ic;
225        char buf[1024];
226       
227        /* Create the groupchat structure. */
228        g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
229        sb->chat = imcb_chat_new( ic, buf );
230       
231        /* Populate the channel. */
232        if( sb->who ) imcb_chat_add_buddy( sb->chat, sb->who );
233        imcb_chat_add_buddy( sb->chat, ic->acc->user );
234       
235        /* And make sure the switchboard doesn't look like a regular chat anymore. */
236        if( sb->who )
237        {
238                g_free( sb->who );
239                sb->who = NULL;
240        }
241       
242        return sb->chat;
243}
244
245void msn_sb_destroy( struct msn_switchboard *sb )
246{
247        struct im_connection *ic = sb->ic;
248        struct msn_data *md = ic->proto_data;
249       
250        debug( "Destroying switchboard: %s", sb->who ? sb->who : sb->key ? sb->key : "" );
251       
252        msn_msgq_purge( ic, &sb->msgq );
253       
254        if( sb->key ) g_free( sb->key );
255        if( sb->who ) g_free( sb->who );
256       
257        if( sb->chat )
258        {
259                imcb_chat_free( sb->chat );
260        }
261       
262        if( sb->handler )
263        {
264                if( sb->handler->rxq ) g_free( sb->handler->rxq );
265                if( sb->handler->cmd_text ) g_free( sb->handler->cmd_text );
266                g_free( sb->handler );
267        }
268       
269        if( sb->inp ) b_event_remove( sb->inp );
270        closesocket( sb->fd );
271       
272        msn_switchboards = g_slist_remove( msn_switchboards, sb );
273        md->switchboards = g_slist_remove( md->switchboards, sb );
274        g_free( sb );
275}
276
277gboolean msn_sb_connected( gpointer data, gint source, b_input_condition cond )
278{
279        struct msn_switchboard *sb = data;
280        struct im_connection *ic;
281        struct msn_data *md;
282        char buf[1024];
283       
284        /* Are we still alive? */
285        if( !g_slist_find( msn_switchboards, sb ) )
286                return FALSE;
287       
288        ic = sb->ic;
289        md = ic->proto_data;
290       
291        if( source != sb->fd )
292        {
293                debug( "Error %d while connecting to switchboard server", 1 );
294                msn_sb_destroy( sb );
295                return FALSE;
296        }
297       
298        /* Prepare the callback */
299        sb->handler = g_new0( struct msn_handler_data, 1 );
300        sb->handler->fd = sb->fd;
301        sb->handler->rxq = g_new0( char, 1 );
302        sb->handler->data = sb;
303        sb->handler->exec_command = msn_sb_command;
304        sb->handler->exec_message = msn_sb_message;
305       
306        if( sb->session == MSN_SB_NEW )
307                g_snprintf( buf, sizeof( buf ), "USR %d %s %s\r\n", ++sb->trId, ic->acc->user, sb->key );
308        else
309                g_snprintf( buf, sizeof( buf ), "ANS %d %s %s %d\r\n", ++sb->trId, ic->acc->user, sb->key, sb->session );
310       
311        if( msn_sb_write( sb, buf, strlen( buf ) ) )
312                sb->inp = b_input_add( sb->fd, GAIM_INPUT_READ, msn_sb_callback, sb );
313        else
314                debug( "Error %d while connecting to switchboard server", 2 );
315       
316        return FALSE;
317}
318
319static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond )
320{
321        struct msn_switchboard *sb = data;
322        struct im_connection *ic = sb->ic;
323        struct msn_data *md = ic->proto_data;
324       
325        if( msn_handler( sb->handler ) == -1 )
326        {
327                time_t now = time( NULL );
328               
329                if( now - md->first_sb_failure > 600 )
330                {
331                        /* It's not really the first one, but the start of this "series".
332                           With this, the warning below will be shown only if this happens
333                           at least three times in ten minutes. This algorithm isn't
334                           perfect, but for this purpose it will do. */
335                        md->first_sb_failure = now;
336                        md->sb_failures = 0;
337                }
338               
339                debug( "Error: Switchboard died" );
340                if( ++ md->sb_failures >= 3 )
341                        imcb_log( ic, "Warning: Many switchboard failures on MSN connection. "
342                                      "There might be problems delivering your messages." );
343               
344                if( sb->msgq != NULL )
345                {
346                        char buf[1024];
347                       
348                        if( md->msgq == NULL )
349                        {
350                                md->msgq = sb->msgq;
351                        }
352                        else
353                        {
354                                GSList *l;
355                               
356                                for( l = md->msgq; l->next; l = l->next );
357                                l->next = sb->msgq;
358                        }
359                        sb->msgq = NULL;
360                       
361                        debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." );
362                        g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
363                        if( !msn_write( ic, buf, strlen( buf ) ) )
364                                return FALSE;
365                }
366               
367                msn_sb_destroy( sb );
368               
369                return FALSE;
370        }
371        else
372        {
373                return TRUE;
374        }
375}
376
377static int msn_sb_command( gpointer data, char **cmd, int num_parts )
378{
379        struct msn_switchboard *sb = data;
380        struct im_connection *ic = sb->ic;
381        char buf[1024];
382       
383        if( !num_parts )
384        {
385                /* Hrrm... Empty command...? Ignore? */
386                return( 1 );
387        }
388       
389        if( strcmp( cmd[0], "XFR" ) == 0 )
390        {
391                imcb_error( ic, "Received an XFR from a switchboard server, unable to comply! This is likely to be a bug, please report it!" );
392                imc_logout( ic, TRUE );
393                return( 0 );
394        }
395        else if( strcmp( cmd[0], "USR" ) == 0 )
396        {
397                if( num_parts != 5 )
398                {
399                        msn_sb_destroy( sb );
400                        return( 0 );
401                }
402               
403                if( strcmp( cmd[2], "OK" ) != 0 )
404                {
405                        msn_sb_destroy( sb );
406                        return( 0 );
407                }
408               
409                if( sb->who )
410                {
411                        g_snprintf( buf, sizeof( buf ), "CAL %d %s\r\n", ++sb->trId, sb->who );
412                        return( msn_sb_write( sb, buf, strlen( buf ) ) );
413                }
414                else
415                {
416                        debug( "Just created a switchboard, but I don't know what to do with it." );
417                }
418        }
419        else if( strcmp( cmd[0], "IRO" ) == 0 )
420        {
421                int num, tot;
422               
423                if( num_parts != 6 )
424                {
425                        msn_sb_destroy( sb );
426                        return( 0 );
427                }
428               
429                num = atoi( cmd[2] );
430                tot = atoi( cmd[3] );
431               
432                if( tot <= 0 )
433                {
434                        msn_sb_destroy( sb );
435                        return( 0 );
436                }
437                else if( tot > 1 )
438                {
439                        char buf[1024];
440                       
441                        if( num == 1 )
442                        {
443                                g_snprintf( buf, sizeof( buf ), "MSN groupchat session %d", sb->session );
444                                sb->chat = imcb_chat_new( ic, buf );
445                               
446                                g_free( sb->who );
447                                sb->who = NULL;
448                        }
449                       
450                        imcb_chat_add_buddy( sb->chat, cmd[4] );
451                       
452                        if( num == tot )
453                        {
454                                imcb_chat_add_buddy( sb->chat, ic->acc->user );
455                        }
456                }
457        }
458        else if( strcmp( cmd[0], "ANS" ) == 0 )
459        {
460                if( num_parts != 3 )
461                {
462                        msn_sb_destroy( sb );
463                        return( 0 );
464                }
465               
466                if( strcmp( cmd[2], "OK" ) != 0 )
467                {
468                        debug( "Switchboard server sent a negative ANS reply" );
469                        msn_sb_destroy( sb );
470                        return( 0 );
471                }
472               
473                sb->ready = 1;
474        }
475        else if( strcmp( cmd[0], "CAL" ) == 0 )
476        {
477                if( num_parts != 4 || !isdigit( cmd[3][0] ) )
478                {
479                        msn_sb_destroy( sb );
480                        return( 0 );
481                }
482               
483                sb->session = atoi( cmd[3] );
484        }
485        else if( strcmp( cmd[0], "JOI" ) == 0 )
486        {
487                if( num_parts != 3 )
488                {
489                        msn_sb_destroy( sb );
490                        return( 0 );
491                }
492               
493                if( sb->who && g_strcasecmp( cmd[1], sb->who ) == 0 )
494                {
495                        /* The user we wanted to talk to is finally there, let's send the queued messages then. */
496                        struct msn_message *m;
497                        GSList *l;
498                        int st = 1;
499                       
500                        debug( "%s arrived in the switchboard session, now sending queued message(s)", cmd[1] );
501                       
502                        /* Without this, sendmessage() will put everything back on the queue... */
503                        sb->ready = 1;
504                       
505                        while( ( l = sb->msgq ) )
506                        {
507                                m = l->data;
508                                if( st )
509                                {
510                                        /* This hack is meant to convert a regular new chat into a groupchat */
511                                        if( strcmp( m->text, GROUPCHAT_SWITCHBOARD_MESSAGE ) == 0 )
512                                                msn_sb_to_chat( sb );
513                                        else
514                                                st = msn_sb_sendmessage( sb, m->text );
515                                }
516                                g_free( m->text );
517                                g_free( m->who );
518                                g_free( m );
519                               
520                                sb->msgq = g_slist_remove( sb->msgq, m );
521                        }
522                       
523                        return( st );
524                }
525                else if( sb->who )
526                {
527                        debug( "Converting chat with %s to a groupchat because %s joined the session.", sb->who, cmd[1] );
528                       
529                        /* This SB is a one-to-one chat right now, but someone else is joining. */
530                        msn_sb_to_chat( sb );
531                       
532                        imcb_chat_add_buddy( sb->chat, cmd[1] );
533                }
534                else if( sb->chat )
535                {
536                        imcb_chat_add_buddy( sb->chat, cmd[1] );
537                        sb->ready = 1;
538                }
539                else
540                {
541                        /* PANIC! */
542                }
543        }
544        else if( strcmp( cmd[0], "MSG" ) == 0 )
545        {
546                if( num_parts != 4 )
547                {
548                        msn_sb_destroy( sb );
549                        return( 0 );
550                }
551               
552                sb->handler->msglen = atoi( cmd[3] );
553               
554                if( sb->handler->msglen <= 0 )
555                {
556                        debug( "Received a corrupted message on the switchboard, the switchboard will be closed" );
557                        msn_sb_destroy( sb );
558                        return( 0 );
559                }
560        }
561        else if( strcmp( cmd[0], "NAK" ) == 0 )
562        {
563                if( sb->who )
564                {
565                        imcb_log( ic, "The MSN servers could not deliver one of your messages to %s.", sb->who );
566                }
567                else
568                {
569                        imcb_log( ic, "The MSN servers could not deliver one of your groupchat messages to all participants." );
570                }
571        }
572        else if( strcmp( cmd[0], "BYE" ) == 0 )
573        {
574                if( num_parts < 2 )
575                {
576                        msn_sb_destroy( sb );
577                        return( 0 );
578                }
579               
580                /* if( cmd[2] && *cmd[2] == '1' ) -=> Chat is being cleaned up because of idleness */
581               
582                if( sb->who )
583                {
584                        /* This is a single-person chat, and the other person is leaving. */
585                        g_free( sb->who );
586                        sb->who = NULL;
587                        sb->ready = 0;
588                       
589                        debug( "Person %s left the one-to-one switchboard connection. Keeping it around as a spare...", cmd[1] );
590                       
591                        /* We could clean up the switchboard now, but keeping it around
592                           as a spare for a next conversation sounds more sane to me.
593                           The server will clean it up when it's idle for too long. */
594                }
595                else if( sb->chat )
596                {
597                        imcb_chat_remove_buddy( sb->chat, cmd[1], "" );
598                }
599                else
600                {
601                        /* PANIC! */
602                }
603        }
604        else if( isdigit( cmd[0][0] ) )
605        {
606                int num = atoi( cmd[0] );
607                const struct msn_status_code *err = msn_status_by_number( num );
608               
609                /* If the person is offline, send an offline message instead,
610                   and don't report an error. */
611                if( num == 217 )
612                        msn_soap_oim_send_queue( ic, &sb->msgq );
613                else
614                        imcb_error( ic, "Error reported by switchboard server: %s", err->text );
615               
616                if( err->flags & STATUS_SB_FATAL )
617                {
618                        msn_sb_destroy( sb );
619                        return 0;
620                }
621                else if( err->flags & STATUS_FATAL )
622                {
623                        imc_logout( ic, TRUE );
624                        return 0;
625                }
626                else if( err->flags & STATUS_SB_IM_SPARE )
627                {
628                        if( sb->who )
629                        {
630                                /* Apparently some invitation failed. We might want to use this
631                                   board later, so keep it as a spare. */
632                                g_free( sb->who );
633                                sb->who = NULL;
634                               
635                                /* Also clear the msgq, otherwise someone else might get them. */
636                                msn_msgq_purge( ic, &sb->msgq );
637                        }
638                       
639                        /* Do NOT return 0 here, we want to keep this sb. */
640                }
641        }
642        else
643        {
644                /* debug( "Received unknown command from switchboard server: %s", cmd[0] ); */
645        }
646       
647        return( 1 );
648}
649
650static int msn_sb_message( gpointer data, char *msg, int msglen, char **cmd, int num_parts )
651{
652        struct msn_switchboard *sb = data;
653        struct im_connection *ic = sb->ic;
654        char *body;
655        int blen = 0;
656       
657        if( !num_parts )
658                return( 1 );
659       
660        if( ( body = strstr( msg, "\r\n\r\n" ) ) )
661        {
662                body += 4;
663                blen = msglen - ( body - msg );
664        }
665       
666        if( strcmp( cmd[0], "MSG" ) == 0 )
667        {
668                char *ct = msn_findheader( msg, "Content-Type:", msglen );
669               
670                if( !ct )
671                        return( 1 );
672               
673                if( g_strncasecmp( ct, "text/plain", 10 ) == 0 )
674                {
675                        g_free( ct );
676                       
677                        if( !body )
678                                return( 1 );
679                       
680                        if( sb->who )
681                        {
682                                imcb_buddy_msg( ic, cmd[1], body, 0, 0 );
683                        }
684                        else if( sb->chat )
685                        {
686                                imcb_chat_msg( sb->chat, cmd[1], body, 0, 0 );
687                        }
688                        else
689                        {
690                                /* PANIC! */
691                        }
692                }
693                else if( g_strncasecmp( ct, "text/x-msmsgsinvite", 19 ) == 0 )
694                {
695                        char *itype = msn_findheader( body, "Application-GUID:", blen );
696                        char buf[1024];
697                       
698                        g_free( ct );
699                       
700                        *buf = 0;
701                       
702                        if( !itype )
703                                return( 1 );
704                       
705                        /* File transfer. */
706                        if( strcmp( itype, "{5D3E02AB-6190-11d3-BBBB-00C04F795683}" ) == 0 )
707                        {
708                                char *name = msn_findheader( body, "Application-File:", blen );
709                                char *size = msn_findheader( body, "Application-FileSize:", blen );
710                               
711                                if( name && size )
712                                {
713                                        g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Filetransfer: `%s', %s bytes >>\n"
714                                                    "Filetransfers are not supported by BitlBee for now...", name, size );
715                                }
716                                else
717                                {
718                                        strcpy( buf, "<< \x02""BitlBee\x02"" - Corrupted MSN filetransfer invitation message >>" );
719                                }
720                               
721                                if( name ) g_free( name );
722                                if( size ) g_free( size );
723                        }
724                        else
725                        {
726                                char *iname = msn_findheader( body, "Application-Name:", blen );
727                               
728                                g_snprintf( buf, sizeof( buf ), "<< \x02""BitlBee\x02"" - Unknown MSN invitation - %s (%s) >>",
729                                                                itype, iname ? iname : "no name" );
730                               
731                                if( iname ) g_free( iname );
732                        }
733                       
734                        g_free( itype );
735                       
736                        if( !*buf )
737                                return( 1 );
738                       
739                        if( sb->who )
740                        {
741                                imcb_buddy_msg( ic, cmd[1], buf, 0, 0 );
742                        }
743                        else if( sb->chat )
744                        {
745                                imcb_chat_msg( sb->chat, cmd[1], buf, 0, 0 );
746                        }
747                        else
748                        {
749                                /* PANIC! */
750                        }
751                }
752                else if( g_strncasecmp( ct, "text/x-msmsgscontrol", 20 ) == 0 )
753                {
754                        char *who = msn_findheader( msg, "TypingUser:", msglen );
755                       
756                        if( who )
757                        {
758                                imcb_buddy_typing( ic, who, OPT_TYPING );
759                                g_free( who );
760                        }
761                       
762                        g_free( ct );
763                }
764                else
765                {
766                        g_free( ct );
767                }
768        }
769       
770        return( 1 );
771}
Note: See TracBrowser for help on using the repository browser.