source: protocols/msn/sb.c @ 8240840

Last change on this file since 8240840 was 17a6ee9, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-04-11T14:37:06Z

Including DCC stuff again, with a wonderful extra layer of abstraction.
Some hooks are missing so sending files doesn't work yet. Receiving also
still seems to have some issues. On the plus side, at least the MSN/Jabber
modules work again.

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