source: protocols/msn/sb.c @ 07ff8a2

Last change on this file since 07ff8a2 was 59f527b6, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-01-12T17:24:38Z

When a switchboard connection dies (at the TCP level) and there are still
queued messages, they will now be moved back to the main queue and a new
sb will be created to try to send the messages again. I hope this will
solve some/most/all of the "Closing switchboard with unsent messages"
problems, but can't be sure since this problem isn't very easy to reproduce.
At least it should solve the ones caused by keeping spare switchboards
around. Also enabling switchboard debugging output if configured with
--debug=1, at least for now this will be useful.

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