source: protocols/msn/sb.c @ a9b1e0e

Last change on this file since a9b1e0e was 8bcd160, checked in by dequis <dx@…>, at 2015-04-06T16:22:05Z

msn: remove old/broken/unused msnftp stuff (requires distclean!)

Since this removes invitation.h, do "make distclean" to fix build errors

MSNFTP is a file transfer method used by early msn messenger versions,
and has been replaced by MSNP2P probably 10 years ago.

This code was disabled/commented out in bitlbee

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