source: protocols/jabber/conference.c @ d11822a

Last change on this file since d11822a was a04705b, checked in by dequis <dx@…>, at 2016-12-26T23:07:56Z

jabber: Workaround for servers (like slack) that send echoes without id

Just comparing the body of the last sent message. This isn't foolproof
and sending several messages quickly can make it fail, but it's less
annoying than before. The correct solution is still to fix the server.

In the case of slack I still recommend using the irc gateway instead.

  • Property mode set to 100644
File size: 14.9 KB
RevLine 
[e35d1a1]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - Conference rooms                                         *
5*                                                                           *
[0e788f5]6*  Copyright 2007-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
[e35d1a1]7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
[fc0640e]25#include "sha1.h"
[e35d1a1]26
[5ebff60]27static xt_status jabber_chat_join_failed(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[fb2338d]28static xt_status jabber_chat_self_message(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[5bd21df]29
[3320d6d]30struct groupchat *jabber_chat_join(struct im_connection *ic, const char *room, const char *nick, const char *password,
31                                   gboolean always_use_nicks)
[e35d1a1]32{
33        struct jabber_chat *jc;
34        struct xt_node *node;
35        struct groupchat *c;
36        char *roomjid;
[5ebff60]37
38        roomjid = g_strdup_printf("%s/%s", room, nick);
39        node = xt_new_node("x", NULL, NULL);
40        xt_add_attr(node, "xmlns", XMLNS_MUC);
41        if (password) {
42                xt_add_child(node, xt_new_node("password", password, NULL));
43        }
44        node = jabber_make_packet("presence", NULL, roomjid, node);
45        jabber_cache_add(ic, node, jabber_chat_join_failed);
46
47        if (!jabber_write_packet(ic, node)) {
48                g_free(roomjid);
[e35d1a1]49                return NULL;
50        }
[5ebff60]51
52        jc = g_new0(struct jabber_chat, 1);
53        jc->name = jabber_normalize(room);
54
55        if ((jc->me = jabber_buddy_add(ic, roomjid)) == NULL) {
56                g_free(roomjid);
57                g_free(jc->name);
58                g_free(jc);
[e35d1a1]59                return NULL;
60        }
[5ebff60]61
[3320d6d]62        if (always_use_nicks) {
63                jc->flags = JCFLAG_ALWAYS_USE_NICKS;
64        }
65
[9c9b37c]66        /* roomjid isn't normalized yet, and we need an original version
67           of the nick to send a proper presence update. */
68        jc->my_full_jid = roomjid;
[5ebff60]69
70        c = imcb_chat_new(ic, room);
[e35d1a1]71        c->data = jc;
[5ebff60]72
[e35d1a1]73        return c;
74}
75
[5ebff60]76struct groupchat *jabber_chat_with(struct im_connection *ic, char *who)
[fc0640e]77{
78        struct jabber_data *jd = ic->proto_data;
79        struct jabber_chat *jc;
80        struct groupchat *c;
81        sha1_state_t sum;
82        double now = gettime();
83        char *uuid, *rjid, *cserv;
[5ebff60]84
85        sha1_init(&sum);
86        sha1_append(&sum, (uint8_t *) ic->acc->user, strlen(ic->acc->user));
87        sha1_append(&sum, (uint8_t *) &now, sizeof(now));
88        sha1_append(&sum, (uint8_t *) who, strlen(who));
89        uuid = sha1_random_uuid(&sum);
90
91        if (jd->flags & JFLAG_GTALK) {
92                cserv = g_strdup("groupchat.google.com");
93        } else {
[fc0640e]94                /* Guess... */
[5ebff60]95                cserv = g_strdup_printf("conference.%s", jd->server);
96        }
97
98        rjid = g_strdup_printf("private-chat-%s@%s", uuid, cserv);
99        g_free(uuid);
100        g_free(cserv);
101
[3320d6d]102        c = jabber_chat_join(ic, rjid, jd->username, NULL, FALSE);
[5ebff60]103        g_free(rjid);
104        if (c == NULL) {
[fc0640e]105                return NULL;
[5ebff60]106        }
107
[fc0640e]108        jc = c->data;
[5ebff60]109        jc->invite = g_strdup(who);
110
[fc0640e]111        return c;
112}
113
[5ebff60]114static xt_status jabber_chat_join_failed(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[5bd21df]115{
116        struct jabber_error *err;
117        struct jabber_buddy *bud;
118        char *room;
[5ebff60]119
120        room = xt_find_attr(orig, "to");
121        bud = jabber_buddy_by_jid(ic, room, 0);
122        err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR);
123        if (err) {
124                imcb_error(ic, "Error joining groupchat %s: %s%s%s", room, err->code,
125                           err->text ? ": " : "", err->text ? err->text : "");
126                jabber_error_free(err);
[5bd21df]127        }
[5ebff60]128        if (bud) {
[c34247d]129                struct groupchat *c = jabber_chat_by_jid(ic, bud->bare_jid);
130                if (c) {
131                        jabber_chat_free(c);
132                }
[5ebff60]133        }
134
[80e9db9]135        return XT_HANDLED;
[5bd21df]136}
137
[fb2338d]138static xt_status jabber_chat_self_message(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
139{
140        /* This is a self message sent by this bitlbee - just drop it */
141        return XT_ABORT;
142}
143
[5ebff60]144struct groupchat *jabber_chat_by_jid(struct im_connection *ic, const char *name)
[5bd21df]145{
[5ebff60]146        char *normalized = jabber_normalize(name);
[aea8b68]147        GSList *l;
[5bd21df]148        struct groupchat *ret;
149        struct jabber_chat *jc;
[5ebff60]150
151        for (l = ic->groupchats; l; l = l->next) {
[aea8b68]152                ret = l->data;
[5bd21df]153                jc = ret->data;
[5ebff60]154                if (strcmp(normalized, jc->name) == 0) {
[5bd21df]155                        break;
[5ebff60]156                }
[5bd21df]157        }
[5ebff60]158        g_free(normalized);
159
[58f5ef7]160        return l ? ret : NULL;
[5bd21df]161}
162
[5ebff60]163void jabber_chat_free(struct groupchat *c)
[9da0bbf]164{
165        struct jabber_chat *jc = c->data;
[5ebff60]166
167        jabber_buddy_remove_bare(c->ic, jc->name);
168
[a04705b]169        g_free(jc->last_sent_message);
[5ebff60]170        g_free(jc->my_full_jid);
171        g_free(jc->name);
172        g_free(jc->invite);
173        g_free(jc);
174
175        imcb_chat_free(c);
[9da0bbf]176}
177
[5ebff60]178int jabber_chat_msg(struct groupchat *c, char *message, int flags)
[43671b9]179{
180        struct im_connection *ic = c->ic;
181        struct jabber_chat *jc = c->data;
182        struct xt_node *node;
[5ebff60]183
[63075d7]184        jc->flags |= JCFLAG_MESSAGE_SENT;
[5ebff60]185
186        node = xt_new_node("body", message, NULL);
187        node = jabber_make_packet("message", "groupchat", jc->name, node);
188
[fb2338d]189        jabber_cache_add(ic, node, jabber_chat_self_message);
[5ebff60]190
[a04705b]191        g_free(jc->last_sent_message);
192        jc->last_sent_message = g_strdup(message);
193
[fb2338d]194        return !jabber_write_packet(ic, node);
[43671b9]195}
196
[5ebff60]197int jabber_chat_topic(struct groupchat *c, char *topic)
[ef5c185]198{
199        struct im_connection *ic = c->ic;
200        struct jabber_chat *jc = c->data;
201        struct xt_node *node;
[5ebff60]202
203        node = xt_new_node("subject", topic, NULL);
204        node = jabber_make_packet("message", "groupchat", jc->name, node);
205
206        if (!jabber_write_packet(ic, node)) {
207                xt_free_node(node);
[ef5c185]208                return 0;
209        }
[5ebff60]210        xt_free_node(node);
211
[ef5c185]212        return 1;
213}
214
[5ebff60]215int jabber_chat_leave(struct groupchat *c, const char *reason)
[e35d1a1]216{
217        struct im_connection *ic = c->ic;
218        struct jabber_chat *jc = c->data;
219        struct xt_node *node;
[5ebff60]220
221        node = xt_new_node("x", NULL, NULL);
222        xt_add_attr(node, "xmlns", XMLNS_MUC);
223        node = jabber_make_packet("presence", "unavailable", jc->my_full_jid, node);
224
225        if (!jabber_write_packet(ic, node)) {
226                xt_free_node(node);
[e35d1a1]227                return 0;
228        }
[5ebff60]229        xt_free_node(node);
230
[e35d1a1]231        return 1;
232}
233
[5ebff60]234void jabber_chat_invite(struct groupchat *c, char *who, char *message)
[c058ff9]235{
236        struct xt_node *node;
237        struct im_connection *ic = c->ic;
238        struct jabber_chat *jc = c->data;
239
[5ebff60]240        node = xt_new_node("reason", message, NULL);
241
242        node = xt_new_node("invite", NULL, node);
243        xt_add_attr(node, "to", who);
[c058ff9]244
[5ebff60]245        node = xt_new_node("x", NULL, node);
246        xt_add_attr(node, "xmlns", XMLNS_MUC_USER);
[c058ff9]247
[5ebff60]248        node = jabber_make_packet("message", NULL, jc->name, node);
[c058ff9]249
[5ebff60]250        jabber_write_packet(ic, node);
[c058ff9]251
[5ebff60]252        xt_free_node(node);
[c058ff9]253}
254
[8f8a56f]255static int jabber_chat_has_other_resources(struct im_connection *ic, struct jabber_buddy *bud)
256{
257        struct jabber_buddy *cur;
258
259        for (cur = jabber_buddy_by_jid(ic, bud->bare_jid, GET_BUDDY_FIRST); cur; cur = cur->next) {
260                if (cur != bud && jabber_compare_jid(cur->ext_jid, bud->ext_jid)) {
261                        return TRUE;
262                }
263        }
264       
265        return FALSE;
266}
267
[e35d1a1]268/* Not really the same syntax as the normal pkt_ functions, but this isn't
[0e7ab64]269   called by the xmltree parser directly and this way I can add some extra
[e35d1a1]270   parameters so we won't have to repeat too many things done by the caller
271   already. */
[5ebff60]272void jabber_chat_pkt_presence(struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node)
[e35d1a1]273{
274        struct groupchat *chat;
275        struct xt_node *c;
[5ebff60]276        char *type = xt_find_attr(node, "type");
[68286eb]277        struct jabber_data *jd = ic->proto_data;
[e35d1a1]278        struct jabber_chat *jc;
279        char *s;
[5ebff60]280
281        if ((chat = jabber_chat_by_jid(ic, bud->bare_jid)) == NULL) {
[e35d1a1]282                /* How could this happen?? We could do kill( self, 11 )
283                   now or just wait for the OS to do it. :-) */
284                return;
285        }
[5ebff60]286
[e35d1a1]287        jc = chat->data;
[5ebff60]288
289        if (type == NULL && !(bud->flags & JBFLAG_IS_CHATROOM)) {
[e35d1a1]290                bud->flags |= JBFLAG_IS_CHATROOM;
291                /* If this one wasn't set yet, this buddy just joined the chat.
292                   Slightly hackish way of finding out eh? ;-) */
[5ebff60]293
[b9f8b87]294                /* This is pretty messy... Here it sets ext_jid to the real
295                   JID of the participant. Works for non-anonymized channels.
296                   Might break if someone joins a chat twice, though. */
[5ebff60]297                for (c = node->children; (c = xt_find_node(c, "x")); c = c->next) {
298                        if ((s = xt_find_attr(c, "xmlns")) &&
299                            (strcmp(s, XMLNS_MUC_USER) == 0)) {
[19176513]300                                struct xt_node *item;
[5ebff60]301
302                                item = xt_find_node(c->children, "item");
303                                if ((s = xt_find_attr(item, "jid"))) {
[e35d1a1]304                                        /* Yay, found what we need. :-) */
[5ebff60]305                                        bud->ext_jid = jabber_normalize(s);
[e35d1a1]306                                        break;
307                                }
308                        }
[5ebff60]309                }
310
[6286f80]311                /* Make up some other handle, if necessary. */
[5ebff60]312                if (bud->ext_jid == NULL) {
313                        if (bud == jc->me) {
314                                bud->ext_jid = g_strdup(jd->me);
315                        } else {
[5d7dc00]316                                int i;
[5ebff60]317
[54f2f55]318                                /* Don't want the nick to be at the end, so let's
319                                   think of some slightly different notation to use
320                                   for anonymous groupchat participants in BitlBee. */
[5ebff60]321                                bud->ext_jid = g_strdup_printf("%s=%s", bud->resource, bud->bare_jid);
322
[5d7dc00]323                                /* And strip any unwanted characters. */
[5ebff60]324                                for (i = 0; bud->resource[i]; i++) {
325                                        if (bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@') {
[5d7dc00]326                                                bud->ext_jid[i] = '_';
[5ebff60]327                                        }
328                                }
[5d7dc00]329                        }
[6286f80]330                        bud->flags |= JBFLAG_IS_ANONYMOUS;
331                }
[5ebff60]332
333                if (bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS) {
[e73a501]334                        /* If JIDs are anonymized, add them to the local
335                           list for the duration of this chat. */
[5ebff60]336                        imcb_add_buddy(ic, bud->ext_jid, NULL);
337                        imcb_buddy_nick_hint(ic, bud->ext_jid, bud->resource);
[c570c86]338                }
[5ebff60]339
340                if (bud == jc->me && jc->invite != NULL) {
341                        char *msg = g_strdup_printf("Please join me in room %s", jc->name);
342                        jabber_chat_invite(chat, jc->invite, msg);
343                        g_free(jc->invite);
344                        g_free(msg);
[fc0640e]345                        jc->invite = NULL;
346                }
[5ebff60]347
348                s = strchr(bud->ext_jid, '/');
349                if (s) {
350                        *s = 0; /* Should NEVER be NULL, but who knows... */
351                }
[3320d6d]352
353                if (bud != jc->me && (jc->flags & JCFLAG_ALWAYS_USE_NICKS) && !(bud->flags & JBFLAG_IS_ANONYMOUS)) {
354                        imcb_buddy_nick_change(ic, bud->ext_jid, bud->resource);
355                }
356
[5ebff60]357                imcb_chat_add_buddy(chat, bud->ext_jid);
358                if (s) {
359                        *s = '/';
360                }
361        } else if (type) { /* type can only be NULL or "unavailable" in this function */
[8f8a56f]362                if ((bud->flags & JBFLAG_IS_CHATROOM) && bud->ext_jid && !jabber_chat_has_other_resources(ic, bud)) {
[83f179d6]363                        char *reason = NULL;
[3c23681]364                        char *status = NULL;
365                        char *status_text = NULL;
[83f179d6]366                       
367                        if ((c = xt_find_node_by_attr(node->children, "x", "xmlns", XMLNS_MUC_USER))) {
[3c23681]368                                struct xt_node *c2 = c->children;
369
370                                while ((c2 = xt_find_node(c2, "status"))) {
371                                        char *code = xt_find_attr(c2, "code");
372                                        if (g_strcmp0(code, "301") == 0) {
[83f179d6]373                                                status = "Banned";
[3c23681]374                                                break;
375                                        } else if (g_strcmp0(code, "303") == 0) {
[83f179d6]376                                                /* This could be handled in a cleverer way,
377                                                 * but let's just show a literal part/join for now */
378                                                status = "Changing nicks";
[3c23681]379                                                break;
380                                        } else if (g_strcmp0(code, "307") == 0) {
[83f179d6]381                                                status = "Kicked";
[3c23681]382                                                break;
[83f179d6]383                                        }
[3c23681]384                                        c2 = c2->next;
[83f179d6]385                                }
386
[3c23681]387                                /* Sometimes the status message is in presence/x/item/reason */
[83f179d6]388                                if ((c2 = xt_find_path(c, "item/reason")) && c2->text && c2->text_len) {
[3c23681]389                                        status_text = c2->text;
[83f179d6]390                                }
391                        }
392
[3c23681]393                        /* Sometimes the status message is right inside <presence> */
394                        if ((c = xt_find_node(node->children, "status")) && c->text && c->text_len) {
395                                status_text = c->text;
396                        }
397
398                        if (status_text && status) {
399                                reason = g_strdup_printf("%s: %s", status, status_text);
400                        } else {
401                                reason = g_strdup(status_text ? : status);
402                        }
403
[5ebff60]404                        s = strchr(bud->ext_jid, '/');
405                        if (s) {
406                                *s = 0;
407                        }
[83f179d6]408                        imcb_chat_remove_buddy(chat, bud->ext_jid, reason);
[5ebff60]409                        if (bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS) {
[83f179d6]410                                imcb_remove_buddy(ic, bud->ext_jid, reason);
[5ebff60]411                        }
412                        if (s) {
413                                *s = '/';
414                        }
[83f179d6]415
416                        g_free(reason);
[5ebff60]417                }
418
419                if (bud == jc->me) {
420                        jabber_chat_free(chat);
[bdad407]421                }
[e35d1a1]422        }
423}
424
[5ebff60]425void jabber_chat_pkt_message(struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node)
[e35d1a1]426{
[5ebff60]427        struct xt_node *subject = xt_find_node(node->children, "subject");
428        struct xt_node *body = xt_find_node(node->children, "body");
[3d31618]429        struct groupchat *chat = NULL;
430        struct jabber_chat *jc = NULL;
431        char *from = NULL;
432        char *nick = NULL;
433        char *final_from = NULL;
434        char *bare_jid = NULL;
[fb2338d]435        guint32 flags = 0;
[5ebff60]436
[3d31618]437        from = (bud) ? bud->full_jid : xt_find_attr(node, "from");
438
439        if (from) {
440                nick = strchr(from, '/');
441                if (nick) {
442                        *nick = 0;
[5ebff60]443                }
[3d31618]444                chat = jabber_chat_by_jid(ic, from);
445                if (nick) {
446                        *nick = '/';
447                        nick++;
[5ebff60]448                }
[41e0c00]449        }
[5ebff60]450
[3d31618]451        jc = (chat) ? chat->data : NULL;
[5ebff60]452
[3d31618]453        if (!bud) {
454                struct xt_node *c;
455                char *s;
456
457                /* Try some clever stuff to find out the real JID here */
458                c = xt_find_node_by_attr(node->children, "delay", "xmlns", XMLNS_DELAY);
459
460                if (c && ((s = xt_find_attr(c, "from")) ||
461                          (s = xt_find_attr(c, "from_jid")))) {
462                        /* This won't be useful if it's the MUC JID */
463                        if (!(jc && jabber_compare_jid(s, jc->name))) {
464                                /* Hopefully this one makes more sense! */
465                                bud = jabber_buddy_by_jid(ic, s, GET_BUDDY_FIRST | GET_BUDDY_CREAT);
466                        }
[5ebff60]467                }
468
[3d31618]469        }
[5ebff60]470
[3d31618]471        if (subject && chat) {
[9698fc0]472                char empty[1] = "";
473                char *subject_text = subject->text_len > 0 ? subject->text : empty;
[3d31618]474                if (g_strcmp0(chat->topic, subject_text) != 0) {
475                        bare_jid = (bud) ? jabber_get_bare_jid(bud->ext_jid) : NULL;
476                        imcb_chat_topic(chat, bare_jid, subject_text,
477                                        jabber_get_timestamp(node));
478                        g_free(bare_jid);
[3cbf71d]479                        bare_jid = NULL;
[a882d6c]480                }
[3d31618]481        }
[5ebff60]482
[3d31618]483        if (body == NULL || body->text_len == 0) {
484                /* Meh. Empty messages aren't very interesting, no matter
485                   how much some servers love to send them. */
486                return;
487        }
488
489        if (chat == NULL) {
[5ebff60]490                if (nick == NULL) {
[3d31618]491                        imcb_log(ic, "System message from unknown groupchat %s: %s", from, body->text);
[5ebff60]492                } else {
[3d31618]493                        imcb_log(ic, "Groupchat message from unknown JID %s: %s", from, body->text);
[a882d6c]494                }
[5ebff60]495
[e35d1a1]496                return;
[3d31618]497        } else if (chat != NULL && bud == NULL && nick == NULL) {
498                imcb_chat_log(chat, "From conference server: %s", body->text);
499                return;
[a04705b]500        } else if (jc && jc->flags & JCFLAG_MESSAGE_SENT && bud == jc->me) {
501                if (jabber_cache_handle_packet(ic, node) == XT_ABORT) {
502                        /* Self message marked by this bitlbee, don't show it */
503                        return;
504                } else if (xt_find_attr(node, "id") == NULL &&
505                           g_strcmp0(body->text, jc->last_sent_message) == 0) {
506                        /* Some misbehaving servers (like slack) eat the ids and echo anyway.
507                         * Try to detect those cases by comparing against the last sent message. */
508                        return;
509                }
[e35d1a1]510        }
[3d31618]511
[fb2338d]512        if (bud) {
[3d31618]513                bare_jid = jabber_get_bare_jid(bud->ext_jid ? bud->ext_jid : bud->full_jid);
514                final_from = bare_jid;
[eee7294]515                if (bud == jc->me || (g_strcasecmp(final_from, ic->acc->user) == 0)) {
516                        flags = OPT_SELFMESSAGE;
517                }
[3d31618]518        } else {
519                final_from = nick;
[e35d1a1]520        }
[3d31618]521
[fb2338d]522        imcb_chat_msg(chat, final_from, body->text, flags, jabber_get_timestamp(node));
[3d31618]523
524        g_free(bare_jid);
[e35d1a1]525}
Note: See TracBrowser for help on using the repository browser.