source: protocols/msn/sb.c @ ccba980

Last change on this file since ccba980 was a830512, checked in by Wilmer van der Gaast <wilmer@…>, at 2008-08-10T09:15:26Z

Added msn_sb_write_msg() (patch from ulim).

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