source: protocols/msn/sb.c @ a2b99ec

Last change on this file since a2b99ec was a2b99ec, checked in by ulim <a.sporto+bee@…>, at 2008-08-10T22:17:58Z

Added MSN file transfer of type MSNFTP.

Transfer is direct and the sender can not be firewalled.

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