source: protocols/jabber/iq.c @ b6df23d

Last change on this file since b6df23d was 399d65a, checked in by dequis <dx@…>, at 2016-10-17T02:46:33Z

jabber: Implement chat list command

Also applies to hipchat.

  • Property mode set to 100644
File size: 33.2 KB
RevLine 
[f06894d]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - IQ packets                                               *
5*                                                                           *
[0e788f5]6*  Copyright 2006-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
[f06894d]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"
[77bfd07]25#include "sha1.h"
[f06894d]26
[5ebff60]27static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
28static xt_status jabber_iq_display_vcard(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[dd43c62]29static xt_status jabber_gmail_handle_new(struct im_connection *ic, struct xt_node *node);
[fa8f57b]30static xt_status jabber_iq_carbons_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[abbd8ed]31
[5ebff60]32xt_status jabber_pkt_iq(struct xt_node *node, gpointer data)
[f06894d]33{
[0da65d5]34        struct im_connection *ic = data;
[68286eb]35        struct jabber_data *jd = ic->proto_data;
[58b5f62]36        struct xt_node *c, *reply = NULL;
[861c199]37        char *type, *s;
[259edd4]38        int st, pack = 1;
[5ebff60]39
40        type = xt_find_attr(node, "type");
41
42        if (!type) {
43                imcb_error(ic, "Received IQ packet without type.");
44                imc_logout(ic, TRUE);
[861c199]45                return XT_ABORT;
46        }
[5ebff60]47
48        if (strcmp(type, "result") == 0 || strcmp(type, "error") == 0) {
49                return jabber_cache_handle_packet(ic, node);
50        } else if (strcmp(type, "get") == 0) {
51                if (!((c = xt_find_node(node->children, "query")) ||
52                      (c = xt_find_node(node->children, "ping")) ||
53                      (c = xt_find_node(node->children, "time"))) ||
54                    !(s = xt_find_attr(c, "xmlns"))) {
[7554702]55
56                        reply = jabber_make_error_packet(node, "service-unavailable", "cancel", NULL);
57                        st = jabber_write_packet(ic, reply);
58                        xt_free_node(reply);
59                        return st;
[58b5f62]60                }
[5ebff60]61
62                reply = xt_new_node("query", NULL, NULL);
63                xt_add_attr(reply, "xmlns", s);
64
[58b5f62]65                /* Of course this is a very essential query to support. ;-) */
[5ebff60]66                if (strcmp(s, XMLNS_VERSION) == 0) {
67                        xt_add_child(reply, xt_new_node("name", set_getstr(&ic->acc->set, "user_agent"), NULL));
68                        xt_add_child(reply, xt_new_node("version", BITLBEE_VERSION, NULL));
69                } else if (strcmp(s, XMLNS_TIME_OLD) == 0) {
[a4effbf]70                        time_t time_ep;
71                        char buf[1024];
[5ebff60]72
73                        buf[sizeof(buf) - 1] = 0;
74                        time_ep = time(NULL);
75
76                        strftime(buf, sizeof(buf) - 1, "%Y%m%dT%H:%M:%S", gmtime(&time_ep));
77                        xt_add_child(reply, xt_new_node("utc", buf, NULL));
78
79                        strftime(buf, sizeof(buf) - 1, "%Z", localtime(&time_ep));
80                        xt_add_child(reply, xt_new_node("tz", buf, NULL));
81                } else if (strcmp(s, XMLNS_TIME) == 0) {
[d76e12f]82                        time_t time_ep;
83                        char buf[1024];
[5ebff60]84
85                        buf[sizeof(buf) - 1] = 0;
86                        time_ep = time(NULL);
87
88                        xt_free_node(reply);
89                        reply = xt_new_node("time", NULL, NULL);
90                        xt_add_attr(reply, "xmlns", XMLNS_TIME);
91
92                        strftime(buf, sizeof(buf) - 1, "%Y%m%dT%H:%M:%SZ", gmtime(&time_ep));
93                        xt_add_child(reply, xt_new_node("utc", buf, NULL));
94
95                        strftime(buf, sizeof(buf) - 1, "%z", localtime(&time_ep));
96                        if (strlen(buf) >= 5) {
[d76e12f]97                                buf[6] = '\0';
98                                buf[5] = buf[4];
99                                buf[4] = buf[3];
100                                buf[3] = ':';
101                        }
[5ebff60]102                        xt_add_child(reply, xt_new_node("tzo", buf, NULL));
103                } else if (strcmp(s, XMLNS_PING) == 0) {
104                        xt_free_node(reply);
105                        reply = jabber_make_packet("iq", "result", xt_find_attr(node, "from"), NULL);
106                        if ((s = xt_find_attr(node, "id"))) {
107                                xt_add_attr(reply, "id", s);
108                        }
[eded1f7]109                        pack = 0;
[5ebff60]110                } else if (strcmp(s, XMLNS_DISCO_INFO) == 0) {
[1ba7e8f]111                        const char *features[] = { XMLNS_DISCO_INFO,
[5ebff60]112                                                   XMLNS_VERSION,
113                                                   XMLNS_TIME_OLD,
114                                                   XMLNS_TIME,
115                                                   XMLNS_CHATSTATES,
116                                                   XMLNS_MUC,
117                                                   XMLNS_PING,
118                                                   XMLNS_RECEIPTS,
119                                                   XMLNS_SI,
120                                                   XMLNS_BYTESTREAMS,
121                                                   XMLNS_FILETRANSFER,
[fa8f57b]122                                                   XMLNS_CARBONS,
[5ebff60]123                                                   NULL };
[40ef702]124                        const char **f;
[5ebff60]125
126                        c = xt_new_node("identity", NULL, NULL);
127                        xt_add_attr(c, "category", "client");
128                        xt_add_attr(c, "type", "pc");
129                        xt_add_attr(c, "name", set_getstr(&ic->acc->set, "user_agent"));
130                        xt_add_child(reply, c);
131
132                        for (f = features; *f; f++) {
133                                c = xt_new_node("feature", NULL, NULL);
134                                xt_add_attr(c, "var", *f);
135                                xt_add_child(reply, c);
[40ef702]136                        }
[5ebff60]137                } else {
138                        xt_free_node(reply);
[7554702]139                        reply = jabber_make_error_packet(node, "service-unavailable", "cancel", NULL);
[259edd4]140                        pack = 0;
[58b5f62]141                }
[5ebff60]142        } else if (strcmp(type, "set") == 0) {
143                if ((c = xt_find_node(node->children, "si")) &&
144                    (s = xt_find_attr(c, "xmlns")) &&
145                    (strcmp(s, XMLNS_SI) == 0)) {
146                        return jabber_si_handle_request(ic, node, c);
[dd43c62]147                } else if ((c = xt_find_node(node->children, "new-mail")) &&
148                           (s = xt_find_attr(c, "xmlns")) &&
149                           (strcmp(s, XMLNS_GMAILNOTIFY) == 0)) {
150                        return jabber_gmail_handle_new(ic, node);
[5ebff60]151                } else if (!(c = xt_find_node(node->children, "query")) ||
152                           !(s = xt_find_attr(c, "xmlns"))) {
[dfa41a4]153                        return XT_HANDLED;
[5ebff60]154                } else if (strcmp(s, XMLNS_ROSTER) == 0) {
155                        /* This is a roster push. XMPP servers send this when someone
156                           was added to (or removed from) the buddy list. AFAIK they're
157                           sent even if we added this buddy in our own session. */
158                        int bare_len = strlen(jd->me);
159
160                        if ((s = xt_find_attr(node, "from")) == NULL ||
161                            (strncmp(s, jd->me, bare_len) == 0 &&
162                             (s[bare_len] == 0 || s[bare_len] == '/'))) {
163                                jabber_parse_roster(ic, node, NULL);
164
[abbd8ed]165                                /* Should we generate a reply here? Don't think it's
166                                   very important... */
[5ebff60]167                        } else {
168                                imcb_log(ic, "Warning: %s tried to fake a roster push!", s ? s : "(unknown)");
169
170                                xt_free_node(reply);
171                                reply = jabber_make_error_packet(node, "not-allowed", "cancel", NULL);
[abbd8ed]172                                pack = 0;
173                        }
[5ebff60]174                } else if (strcmp(s, XMLNS_BYTESTREAMS) == 0) {
[1c3008a]175                        /* Bytestream Request (stage 2 of file transfer) */
[5ebff60]176                        return jabber_bs_recv_request(ic, node, c);
177                } else {
178                        xt_free_node(reply);
179                        reply = jabber_make_error_packet(node, "feature-not-implemented", "cancel", NULL);
[dfa41a4]180                        pack = 0;
181                }
[259edd4]182        }
[5ebff60]183
[259edd4]184        /* If we recognized the xmlns and managed to generate a reply,
185           finish and send it. */
[5ebff60]186        if (reply) {
[259edd4]187                /* Normally we still have to pack it into an iq-result
188                   packet, but for errors, for example, we don't. */
[5ebff60]189                if (pack) {
190                        reply = jabber_make_packet("iq", "result", xt_find_attr(node, "from"), reply);
191                        if ((s = xt_find_attr(node, "id"))) {
192                                xt_add_attr(reply, "id", s);
193                        }
194                }
195
196                st = jabber_write_packet(ic, reply);
197                xt_free_node(reply);
198                if (!st) {
[259edd4]199                        return XT_ABORT;
[5ebff60]200                }
[58b5f62]201        }
[5ebff60]202
[861c199]203        return XT_HANDLED;
204}
205
[5ebff60]206static xt_status jabber_do_iq_auth(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
207static xt_status jabber_finish_iq_auth(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[861c199]208
[5ebff60]209int jabber_init_iq_auth(struct im_connection *ic)
[861c199]210{
[0da65d5]211        struct jabber_data *jd = ic->proto_data;
[861c199]212        struct xt_node *node;
213        int st;
[5ebff60]214
215        node = xt_new_node("query", NULL, xt_new_node("username", jd->username, NULL));
216        xt_add_attr(node, "xmlns", XMLNS_AUTH);
217        node = jabber_make_packet("iq", "get", NULL, node);
218
219        jabber_cache_add(ic, node, jabber_do_iq_auth);
220        st = jabber_write_packet(ic, node);
221
[861c199]222        return st;
223}
224
[5ebff60]225static xt_status jabber_do_iq_auth(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[861c199]226{
[0da65d5]227        struct jabber_data *jd = ic->proto_data;
[861c199]228        struct xt_node *reply, *query;
229        xt_status st;
230        char *s;
[5ebff60]231
232        if (!(query = xt_find_node(node->children, "query"))) {
233                imcb_log(ic, "Warning: Received incomplete IQ packet while authenticating");
234                imc_logout(ic, FALSE);
[b1d9c06]235                return XT_ABORT;
[9bcbe48]236        }
[5ebff60]237
[861c199]238        /* Time to authenticate ourselves! */
[5ebff60]239        reply = xt_new_node("query", NULL, NULL);
240        xt_add_attr(reply, "xmlns", XMLNS_AUTH);
241        xt_add_child(reply, xt_new_node("username", jd->username, NULL));
242        xt_add_child(reply, xt_new_node("resource", set_getstr(&ic->acc->set, "resource"), NULL));
243
244        if (xt_find_node(query->children, "digest") && (s = xt_find_attr(jd->xt->root, "id"))) {
[861c199]245                /* We can do digest authentication, it seems, and of
246                   course we prefer that. */
[77bfd07]247                sha1_state_t sha;
[e727608]248                char hash_hex[41];
[861c199]249                unsigned char hash[20];
250                int i;
[5ebff60]251
252                sha1_init(&sha);
253                sha1_append(&sha, (unsigned char *) s, strlen(s));
254                sha1_append(&sha, (unsigned char *) ic->acc->pass, strlen(ic->acc->pass));
255                sha1_finish(&sha, hash);
256
257                for (i = 0; i < 20; i++) {
258                        sprintf(hash_hex + i * 2, "%02x", hash[i]);
259                }
260
261                xt_add_child(reply, xt_new_node("digest", hash_hex, NULL));
262        } else if (xt_find_node(query->children, "password")) {
[861c199]263                /* We'll have to stick with plaintext. Let's hope we're using SSL/TLS... */
[5ebff60]264                xt_add_child(reply, xt_new_node("password", ic->acc->pass, NULL));
265        } else {
266                xt_free_node(reply);
267
268                imcb_error(ic, "Can't find suitable authentication method");
269                imc_logout(ic, FALSE);
[861c199]270                return XT_ABORT;
[70f6aab8]271        }
[5ebff60]272
273        reply = jabber_make_packet("iq", "set", NULL, reply);
274        jabber_cache_add(ic, reply, jabber_finish_iq_auth);
275        st = jabber_write_packet(ic, reply);
276
[861c199]277        return st ? XT_HANDLED : XT_ABORT;
278}
279
[5ebff60]280static xt_status jabber_finish_iq_auth(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[861c199]281{
[0da65d5]282        struct jabber_data *jd = ic->proto_data;
[9bcbe48]283        char *type;
[5ebff60]284
285        if (!(type = xt_find_attr(node, "type"))) {
286                imcb_log(ic, "Warning: Received incomplete IQ packet while authenticating");
287                imc_logout(ic, FALSE);
[b1d9c06]288                return XT_ABORT;
[9bcbe48]289        }
[5ebff60]290
291        if (strcmp(type, "error") == 0) {
292                imcb_error(ic, "Authentication failure");
293                imc_logout(ic, FALSE);
[861c199]294                return XT_ABORT;
[5ebff60]295        } else if (strcmp(type, "result") == 0) {
[861c199]296                /* This happens when we just successfully authenticated the
297                   old (non-SASL) way. */
298                jd->flags |= JFLAG_AUTHENTICATED;
[5ebff60]299                if (!jabber_get_roster(ic)) {
[70f6aab8]300                        return XT_ABORT;
[5ebff60]301                }
302                if (!jabber_iq_disco_server(ic)) {
[bb2d198]303                        return XT_ABORT;
[5ebff60]304                }
[70f6aab8]305        }
[5ebff60]306
[f06894d]307        return XT_HANDLED;
308}
309
[5ebff60]310xt_status jabber_pkt_bind_sess(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[21167d2]311{
[0da65d5]312        struct jabber_data *jd = ic->proto_data;
[8fb1263]313        struct xt_node *c, *reply = NULL;
[861c199]314        char *s;
[5ebff60]315
316        if (node && (c = xt_find_node(node->children, "bind"))) {
317                c = xt_find_node(c->children, "jid");
318                if (!c || !c->text) {
[68286eb]319                        /* Server is crap, but this is no disaster. */
[5ebff60]320                } else if (jabber_compare_jid(jd->me, c->text) == 0) {
321                        s = strchr(c->text, '/');
322                        if (s) {
[68286eb]323                                *s = '\0';
[5ebff60]324                        }
325                        jabber_set_me(ic, c->text);
326                        if (s) {
[68286eb]327                                *s = '/';
[5ebff60]328                        }
329                } else if (c && c->text_len && (s = strchr(c->text, '/')) &&
330                           strcmp(s + 1, set_getstr(&ic->acc->set, "resource")) != 0) {
331                        imcb_log(ic, "Server changed session resource string to `%s'", s + 1);
[68286eb]332                }
[861c199]333        }
[5ebff60]334
335        if (jd->flags & JFLAG_WANT_BIND) {
336                reply = xt_new_node("bind", NULL, xt_new_node("resource", set_getstr(&ic->acc->set, "resource"), NULL));
337                xt_add_attr(reply, "xmlns", XMLNS_BIND);
[315dd4c]338                jd->flags &= ~JFLAG_WANT_BIND;
[5ebff60]339        } else if (jd->flags & JFLAG_WANT_SESSION) {
340                reply = xt_new_node("session", NULL, NULL);
341                xt_add_attr(reply, "xmlns", XMLNS_SESSION);
[315dd4c]342                jd->flags &= ~JFLAG_WANT_SESSION;
[861c199]343        }
[5ebff60]344
345        if (reply != NULL) {
346                reply = jabber_make_packet("iq", "set", NULL, reply);
347                jabber_cache_add(ic, reply, jabber_pkt_bind_sess);
348
349                if (!jabber_write_packet(ic, reply)) {
[8fb1263]350                        return XT_ABORT;
[5ebff60]351                }
[dd43c62]352                if (jd->flags & JFLAG_GMAILNOTIFY && node == NULL) {
353                        jabber_iq_query_server(ic, jd->server, XMLNS_DISCO_INFO);
354                }
[5ebff60]355        } else if ((jd->flags & (JFLAG_WANT_BIND | JFLAG_WANT_SESSION)) == 0) {
356                if (!jabber_get_roster(ic)) {
[861c199]357                        return XT_ABORT;
[5ebff60]358                }
359                if (!jabber_iq_disco_server(ic)) {
[bb2d198]360                        return XT_ABORT;
[5ebff60]361                }
[861c199]362        }
[5ebff60]363
[861c199]364        return XT_HANDLED;
[21167d2]365}
[70f6aab8]366
[5ebff60]367int jabber_get_roster(struct im_connection *ic)
[70f6aab8]368{
369        struct xt_node *node;
370        int st;
[5ebff60]371
372        imcb_log(ic, "Authenticated, requesting buddy list");
373
374        node = xt_new_node("query", NULL, NULL);
375        xt_add_attr(node, "xmlns", XMLNS_ROSTER);
376        node = jabber_make_packet("iq", "get", NULL, node);
377
378        jabber_cache_add(ic, node, jabber_parse_roster);
379        st = jabber_write_packet(ic, node);
380
[70f6aab8]381        return st;
382}
[cfbb3a6]383
[dd43c62]384xt_status jabber_iq_query_gmail(struct im_connection *ic);
385
386static xt_status jabber_gmail_handle_new(struct im_connection *ic, struct xt_node *node)
387{
388        struct xt_node *response;
389        struct jabber_data *jd = ic->proto_data;
[df215a3]390       
391        response = jabber_make_packet("iq", "result", jd->me, NULL);
[dd43c62]392
393        jabber_cache_add(ic, response, NULL);
394        if (!jabber_write_packet(ic, response)) {
395                return XT_ABORT;
396        }
397
398        jabber_iq_query_gmail(ic);
399
400        return XT_HANDLED;
401}
402
[5ebff60]403static xt_status jabber_parse_roster(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[861c199]404{
[40cfbc5]405        struct jabber_data *jd = ic->proto_data;
[861c199]406        struct xt_node *query, *c;
[5ebff60]407        int initial = (orig != NULL);
408
409        if (!(query = xt_find_node(node->children, "query"))) {
410                imcb_log(ic, "Warning: Received NULL roster packet");
[9bcbe48]411                return XT_HANDLED;
412        }
[5ebff60]413
[861c199]414        c = query->children;
[5ebff60]415        while ((c = xt_find_node(c, "item"))) {
416                struct xt_node *group = xt_find_node(c->children, "group");
417                char *jid = xt_find_attr(c, "jid");
418                char *name = xt_find_attr(c, "name");
419                char *sub = xt_find_attr(c, "subscription");
[40cfbc5]420                char *mention_name = xt_find_attr(c, "mention_name");
[5ebff60]421
422                if (jid && sub) {
423                        if ((strcmp(sub, "both") == 0 || strcmp(sub, "to") == 0)) {
424                                imcb_add_buddy(ic, jid, (group && group->text_len) ?
425                                               group->text : NULL);
426
427                                if (name) {
428                                        imcb_rename_buddy(ic, jid, name);
429                                }
[40cfbc5]430
431                                /* This could also be used to set the full name as nick for fb/gtalk,
432                                 * but i'm keeping the old (ugly?) default behavior just to be safe */
433                                if (mention_name && (jd->flags & JFLAG_HIPCHAT)) {
434                                        imcb_buddy_nick_hint(ic, jid, mention_name);
435                                }
[5ebff60]436                        } else if (strcmp(sub, "remove") == 0) {
437                                jabber_buddy_remove_bare(ic, jid);
438                                imcb_remove_buddy(ic, jid, NULL);
[abbd8ed]439                        }
440                }
[5ebff60]441
[861c199]442                c = c->next;
443        }
[5ebff60]444
445        if (initial) {
446                imcb_connected(ic);
447        }
448
[861c199]449        return XT_HANDLED;
450}
451
[5ebff60]452int jabber_get_vcard(struct im_connection *ic, char *bare_jid)
[1991be6]453{
454        struct xt_node *node;
[5ebff60]455
456        if (strchr(bare_jid, '/')) {
457                return 1;       /* This was an error, but return 0 should only be done if the connection died... */
458
459        }
460        node = xt_new_node("vCard", NULL, NULL);
461        xt_add_attr(node, "xmlns", XMLNS_VCARD);
462        node = jabber_make_packet("iq", "get", bare_jid, node);
463
464        jabber_cache_add(ic, node, jabber_iq_display_vcard);
465        return jabber_write_packet(ic, node);
[1991be6]466}
467
[5ebff60]468static xt_status jabber_iq_display_vcard(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[1991be6]469{
[0da65d5]470        struct xt_node *vc, *c, *sc; /* subchild, ic is already in use ;-) */
[1991be6]471        GString *reply;
472        char *s;
[5ebff60]473
474        if ((s = xt_find_attr(node, "type")) == NULL ||
475            strcmp(s, "result") != 0 ||
476            (vc = xt_find_node(node->children, "vCard")) == NULL) {
477                s = xt_find_attr(orig, "to");   /* If this returns NULL something's wrong.. */
478                imcb_log(ic, "Could not retrieve vCard of %s", s ? s : "(NULL)");
[1991be6]479                return XT_HANDLED;
480        }
[5ebff60]481
482        s = xt_find_attr(orig, "to");
483        reply = g_string_new("vCard information for ");
484        reply = g_string_append(reply, s ? s : "(NULL)");
485        reply = g_string_append(reply, ":\n");
486
[1991be6]487        /* I hate this format, I really do... */
[5ebff60]488
489        if ((c = xt_find_node(vc->children, "FN")) && c->text_len) {
490                g_string_append_printf(reply, "Name: %s\n", c->text);
491        }
492
493        if ((c = xt_find_node(vc->children, "N")) && c->children) {
494                reply = g_string_append(reply, "Full name:");
495
496                if ((sc = xt_find_node(c->children, "PREFIX")) && sc->text_len) {
497                        g_string_append_printf(reply, " %s", sc->text);
498                }
499                if ((sc = xt_find_node(c->children, "GIVEN")) && sc->text_len) {
500                        g_string_append_printf(reply, " %s", sc->text);
501                }
502                if ((sc = xt_find_node(c->children, "MIDDLE")) && sc->text_len) {
503                        g_string_append_printf(reply, " %s", sc->text);
504                }
505                if ((sc = xt_find_node(c->children, "FAMILY")) && sc->text_len) {
506                        g_string_append_printf(reply, " %s", sc->text);
507                }
508                if ((sc = xt_find_node(c->children, "SUFFIX")) && sc->text_len) {
509                        g_string_append_printf(reply, " %s", sc->text);
510                }
511
512                reply = g_string_append_c(reply, '\n');
513        }
514
515        if ((c = xt_find_node(vc->children, "NICKNAME")) && c->text_len) {
516                g_string_append_printf(reply, "Nickname: %s\n", c->text);
517        }
518
519        if ((c = xt_find_node(vc->children, "BDAY")) && c->text_len) {
520                g_string_append_printf(reply, "Date of birth: %s\n", c->text);
521        }
522
[1991be6]523        /* Slightly alternative use of for... ;-) */
[5ebff60]524        for (c = vc->children; (c = xt_find_node(c, "EMAIL")); c = c->next) {
525                if ((sc = xt_find_node(c->children, "USERID")) == NULL || sc->text_len == 0) {
[1991be6]526                        continue;
[5ebff60]527                }
528
529                if (xt_find_node(c->children, "HOME")) {
[1991be6]530                        s = "Home";
[5ebff60]531                } else if (xt_find_node(c->children, "WORK")) {
[1991be6]532                        s = "Work";
[5ebff60]533                } else {
[1991be6]534                        s = "Misc.";
[5ebff60]535                }
536
537                g_string_append_printf(reply, "%s e-mail address: %s\n", s, sc->text);
[1991be6]538        }
[5ebff60]539
540        if ((c = xt_find_node(vc->children, "URL")) && c->text_len) {
541                g_string_append_printf(reply, "Homepage: %s\n", c->text);
542        }
543
[1991be6]544        /* Slightly alternative use of for... ;-) */
[5ebff60]545        for (c = vc->children; (c = xt_find_node(c, "ADR")); c = c->next) {
546                if (xt_find_node(c->children, "HOME")) {
[1991be6]547                        s = "Home";
[5ebff60]548                } else if (xt_find_node(c->children, "WORK")) {
[1991be6]549                        s = "Work";
[5ebff60]550                } else {
[1991be6]551                        s = "Misc.";
[5ebff60]552                }
553
554                g_string_append_printf(reply, "%s address: ", s);
555
556                if ((sc = xt_find_node(c->children, "STREET")) && sc->text_len) {
557                        g_string_append_printf(reply, "%s ", sc->text);
558                }
559                if ((sc = xt_find_node(c->children, "EXTADR")) && sc->text_len) {
560                        g_string_append_printf(reply, "%s, ", sc->text);
561                }
562                if ((sc = xt_find_node(c->children, "PCODE")) && sc->text_len) {
563                        g_string_append_printf(reply, "%s, ", sc->text);
564                }
565                if ((sc = xt_find_node(c->children, "LOCALITY")) && sc->text_len) {
566                        g_string_append_printf(reply, "%s, ", sc->text);
567                }
568                if ((sc = xt_find_node(c->children, "REGION")) && sc->text_len) {
569                        g_string_append_printf(reply, "%s, ", sc->text);
570                }
571                if ((sc = xt_find_node(c->children, "CTRY")) && sc->text_len) {
572                        g_string_append_printf(reply, "%s", sc->text);
573                }
574
575                if (reply->str[reply->len - 2] == ',') {
576                        reply = g_string_truncate(reply, reply->len - 2);
577                }
578
579                reply = g_string_append_c(reply, '\n');
580        }
581
582        for (c = vc->children; (c = xt_find_node(c, "TEL")); c = c->next) {
583                if ((sc = xt_find_node(c->children, "NUMBER")) == NULL || sc->text_len == 0) {
[1991be6]584                        continue;
[5ebff60]585                }
586
587                if (xt_find_node(c->children, "HOME")) {
[1991be6]588                        s = "Home";
[5ebff60]589                } else if (xt_find_node(c->children, "WORK")) {
[1991be6]590                        s = "Work";
[5ebff60]591                } else {
[1991be6]592                        s = "Misc.";
[5ebff60]593                }
594
595                g_string_append_printf(reply, "%s phone number: %s\n", s, sc->text);
596        }
597
598        if ((c = xt_find_node(vc->children, "DESC")) && c->text_len) {
599                g_string_append_printf(reply, "Other information:\n%s", c->text);
[1991be6]600        }
[5ebff60]601
[1991be6]602        /* *sigh* */
[5ebff60]603
604        imcb_log(ic, "%s", reply->str);
605        g_string_free(reply, TRUE);
606
[1991be6]607        return XT_HANDLED;
608}
609
[5ebff60]610static xt_status jabber_add_to_roster_callback(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[a73e91a]611
[5ebff60]612int jabber_add_to_roster(struct im_connection *ic, const char *handle, const char *name, const char *group)
[cfbb3a6]613{
614        struct xt_node *node;
615        int st;
[5ebff60]616
[cfbb3a6]617        /* Build the item entry */
[5ebff60]618        node = xt_new_node("item", NULL, NULL);
619        xt_add_attr(node, "jid", handle);
620        if (name) {
621                xt_add_attr(node, "name", name);
622        }
623        if (group) {
624                xt_add_child(node, xt_new_node("group", group, NULL));
625        }
626
[cfbb3a6]627        /* And pack it into a roster-add packet */
[5ebff60]628        node = xt_new_node("query", NULL, node);
629        xt_add_attr(node, "xmlns", XMLNS_ROSTER);
630        node = jabber_make_packet("iq", "set", NULL, node);
631        jabber_cache_add(ic, node, jabber_add_to_roster_callback);
632
633        st = jabber_write_packet(ic, node);
634
[cfbb3a6]635        return st;
636}
637
[5ebff60]638static xt_status jabber_add_to_roster_callback(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[a73e91a]639{
640        char *s, *jid = NULL;
641        struct xt_node *c;
[5ebff60]642
643        if ((c = xt_find_node(orig->children, "query")) &&
644            (c = xt_find_node(c->children, "item")) &&
645            (jid = xt_find_attr(c, "jid")) &&
646            (s = xt_find_attr(node, "type")) &&
647            strcmp(s, "result") == 0) {
648                if (bee_user_by_handle(ic->bee, ic, jid) == NULL) {
649                        imcb_add_buddy(ic, jid, NULL);
650                }
651        } else {
652                imcb_log(ic, "Error while adding `%s' to your contact list.",
653                         jid ? jid : "(unknown handle)");
654        }
655
[a73e91a]656        return XT_HANDLED;
657}
658
[5ebff60]659int jabber_remove_from_roster(struct im_connection *ic, char *handle)
[cfbb3a6]660{
661        struct xt_node *node;
662        int st;
[5ebff60]663
[cfbb3a6]664        /* Build the item entry */
[5ebff60]665        node = xt_new_node("item", NULL, NULL);
666        xt_add_attr(node, "jid", handle);
667        xt_add_attr(node, "subscription", "remove");
668
[cfbb3a6]669        /* And pack it into a roster-add packet */
[5ebff60]670        node = xt_new_node("query", NULL, node);
671        xt_add_attr(node, "xmlns", XMLNS_ROSTER);
672        node = jabber_make_packet("iq", "set", NULL, node);
673
674        st = jabber_write_packet(ic, node);
675
676        xt_free_node(node);
[cfbb3a6]677        return st;
678}
[dc0ba9c]679
[5ebff60]680xt_status jabber_iq_parse_features(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[dc0ba9c]681
[5ebff60]682xt_status jabber_iq_query_features(struct im_connection *ic, char *bare_jid)
[dc0ba9c]683{
684        struct xt_node *node, *query;
685        struct jabber_buddy *bud;
[5ebff60]686
687        if ((bud = jabber_buddy_by_jid(ic, bare_jid, 0)) == NULL) {
[dc0ba9c]688                /* Who cares about the unknown... */
[5ebff60]689                imcb_log(ic, "Couldn't find buddy: %s", bare_jid);
[54a2014]690                return XT_HANDLED;
[dc0ba9c]691        }
[5ebff60]692
693        if (bud->features) { /* been here already */
[dc0ba9c]694                return XT_HANDLED;
[5ebff60]695        }
696
697        node = xt_new_node("query", NULL, NULL);
698        xt_add_attr(node, "xmlns", XMLNS_DISCO_INFO);
699
700        if (!(query = jabber_make_packet("iq", "get", bare_jid, node))) {
701                imcb_log(ic, "WARNING: Couldn't generate feature query");
702                xt_free_node(node);
[54a2014]703                return XT_HANDLED;
[dc0ba9c]704        }
705
[5ebff60]706        jabber_cache_add(ic, query, jabber_iq_parse_features);
[dc0ba9c]707
[5ebff60]708        return jabber_write_packet(ic, query) ? XT_HANDLED : XT_ABORT;
[dc0ba9c]709}
710
[5ebff60]711xt_status jabber_iq_parse_features(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[dc0ba9c]712{
713        struct xt_node *c;
714        struct jabber_buddy *bud;
[c1a3c27]715        char *feature, *xmlns, *from;
[dc0ba9c]716
[5ebff60]717        if (!(from = xt_find_attr(node, "from")) ||
718            !(c = xt_find_node(node->children, "query")) ||
719            !(xmlns = xt_find_attr(c, "xmlns")) ||
720            !(strcmp(xmlns, XMLNS_DISCO_INFO) == 0)) {
721                imcb_log(ic, "WARNING: Received incomplete IQ-result packet for discover");
[dc0ba9c]722                return XT_HANDLED;
723        }
[5ebff60]724        if ((bud = jabber_buddy_by_jid(ic, from, 0)) == NULL) {
[dc0ba9c]725                /* Who cares about the unknown... */
[5ebff60]726                imcb_log(ic, "Couldn't find buddy: %s", from);
[54a2014]727                return XT_HANDLED;
[dc0ba9c]728        }
[5ebff60]729
[dc0ba9c]730        c = c->children;
[5ebff60]731        while ((c = xt_find_node(c, "feature"))) {
732                feature = xt_find_attr(c, "var");
733                if (feature) {
734                        bud->features = g_slist_append(bud->features, g_strdup(feature));
735                }
[dc0ba9c]736                c = c->next;
737        }
738
739        return XT_HANDLED;
740}
741
[dd43c62]742xt_status jabber_iq_parse_gmail(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
743
744xt_status jabber_iq_query_gmail(struct im_connection *ic)
745{
746        struct xt_node *node, *query;
747        struct jabber_data *jd = ic->proto_data;
748
749        node = xt_new_node("query", NULL, NULL);
750        xt_add_attr(node, "xmlns", XMLNS_GMAILNOTIFY);
751        if (jd->gmail_time) {
752                char *formatted = g_strdup_printf("%" G_GUINT64_FORMAT, (jd->gmail_time + 1));
753                xt_add_attr(node, "newer-than-time", formatted);
754                g_free(formatted);
755        }
756        if (jd->gmail_tid) {
757                xt_add_attr(node, "newer-than-tid", jd->gmail_tid);
758        }
759
760        if (!(query = jabber_make_packet("iq", "get", jd->me, node))) {
761                imcb_log(ic, "WARNING: Couldn't generate server query");
762                xt_free_node(node);
763        }
764
765        jabber_cache_add(ic, query, jabber_iq_parse_gmail);
766
767        return jabber_write_packet(ic, query) ? XT_HANDLED : XT_ABORT;
768}
769
[5ebff60]770xt_status jabber_iq_parse_server_features(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
[dc0ba9c]771
[5ebff60]772xt_status jabber_iq_query_server(struct im_connection *ic, char *jid, char *xmlns)
[dc0ba9c]773{
774        struct xt_node *node, *query;
775        struct jabber_data *jd = ic->proto_data;
[5ebff60]776
777        node = xt_new_node("query", NULL, NULL);
778        xt_add_attr(node, "xmlns", xmlns);
779
780        if (!(query = jabber_make_packet("iq", "get", jid, node))) {
781                imcb_log(ic, "WARNING: Couldn't generate server query");
782                xt_free_node(node);
[dc0ba9c]783        }
784
785        jd->have_streamhosts--;
[5ebff60]786        jabber_cache_add(ic, query, jabber_iq_parse_server_features);
[dc0ba9c]787
[5ebff60]788        return jabber_write_packet(ic, query) ? XT_HANDLED : XT_ABORT;
[dc0ba9c]789}
790
[dd43c62]791xt_status jabber_iq_parse_gmail(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
792{
793        struct xt_node *c;
794        struct jabber_data *jd = ic->proto_data;
795        char *xmlns, *from;
796        guint64 l_time = 0;
797        char *tid = NULL;
[faeb521]798        int max = 0;
[dd43c62]799
800        if (!(c = xt_find_node(node->children, "mailbox")) ||
801            !(from = xt_find_attr(node, "from")) ||
802            !(xmlns = xt_find_attr(c, "xmlns")) ||
803            (g_strcmp0(xmlns, XMLNS_GMAILNOTIFY) != 0)) {
804                imcb_log(ic, "WARNING: Received incomplete mailbox packet for gmail notify");
805                return XT_HANDLED;
806        }
807
[b38f655]808        max = set_getint(&ic->acc->set, "mail_notifications_limit");
[dd43c62]809        c = c->children;
810
[faeb521]811        while ((max-- > 0) && (c = xt_find_node(c, "mail-thread-info"))) {
812                struct xt_node *s;
813                char *subject = "<no subject>";
814                char *sender = "<no sender>";
[dd43c62]815                guint64 t_time;
816
817                t_time = g_ascii_strtoull(xt_find_attr(c, "date"), NULL, 10);
818                if (t_time && t_time > l_time) {
819                        l_time = t_time;
820                        tid = xt_find_attr(c, "tid");
821                }
822
[faeb521]823                if ((s = xt_find_node(c->children, "senders")) &&
824                    (s = xt_find_node_by_attr(s->children, "sender", "unread", "1"))) {
825                        sender = xt_find_attr(s, "name");
826                }
[dd43c62]827
[faeb521]828                if ((s = xt_find_node(c->children, "subject")) && s->text) {
[dd43c62]829                        subject = s->text;
830                }
831
[0864a52]832                imcb_notify_email(ic, "New mail from %s: %s", sender, subject);
[dd43c62]833
834                c = c->next;
835        }
836
837        if (l_time && (!jd->gmail_time || l_time > jd->gmail_time)) {
838                jd->gmail_time = l_time;
839                if (tid) {
840                        g_free(jd->gmail_tid);
841                        jd->gmail_tid = g_strdup(tid);
842                }
843        }
844
845        return XT_HANDLED;
846}
847
[dc0ba9c]848/*
849 * Query the server for "items", query each "item" for identities, query each "item" that's a proxy for it's bytestream info
850 */
[5ebff60]851xt_status jabber_iq_parse_server_features(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
[dc0ba9c]852{
853        struct xt_node *c;
854        struct jabber_data *jd = ic->proto_data;
[c1a3c27]855        char *xmlns, *from;
[dc0ba9c]856
[5ebff60]857        if (!(c = xt_find_node(node->children, "query")) ||
858            !(from = xt_find_attr(node, "from")) ||
859            !(xmlns = xt_find_attr(c, "xmlns"))) {
860                imcb_log(ic, "WARNING: Received incomplete IQ-result packet for discover");
[dc0ba9c]861                return XT_HANDLED;
862        }
863
864        jd->have_streamhosts++;
865
[5ebff60]866        if (strcmp(xmlns, XMLNS_DISCO_ITEMS) == 0) {
[c1a3c27]867                char *itemjid;
[dc0ba9c]868
869                /* answer from server */
[5ebff60]870
[dc0ba9c]871                c = c->children;
[5ebff60]872                while ((c = xt_find_node(c, "item"))) {
873                        itemjid = xt_find_attr(c, "jid");
874
875                        if (itemjid) {
876                                jabber_iq_query_server(ic, itemjid, XMLNS_DISCO_INFO);
877                        }
[dc0ba9c]878
879                        c = c->next;
880                }
[5ebff60]881        } else if (strcmp(xmlns, XMLNS_DISCO_INFO) == 0) {
[dc0ba9c]882                char *category, *type;
883
884                /* answer from potential proxy */
885
886                c = c->children;
[5ebff60]887                while ((c = xt_find_node(c, "identity"))) {
888                        category = xt_find_attr(c, "category");
889                        type = xt_find_attr(c, "type");
[dc0ba9c]890
[5ebff60]891                        if (type && (strcmp(type, "bytestreams") == 0) &&
892                            category && (strcmp(category, "proxy") == 0)) {
893                                jabber_iq_query_server(ic, from, XMLNS_BYTESTREAMS);
894                        }
[dc0ba9c]895
896                        c = c->next;
897                }
[dd43c62]898
899                if (jd->flags & JFLAG_GMAILNOTIFY) {
900                        /* search for gmail notification feature */
901                        c = xt_find_node(node->children, "query");
902                        c = c->children;
903                        while ((c = xt_find_node(c, "feature"))) {
904                                if (strcmp(xt_find_attr(c, "var"), XMLNS_GMAILNOTIFY) == 0) {
905                                        jabber_iq_query_gmail(ic);
906                                }
907                                c = c->next;
908                        }
909                }
910
[5ebff60]911        } else if (strcmp(xmlns, XMLNS_BYTESTREAMS) == 0) {
[c1a3c27]912                char *host, *jid, *port_s;
[dc0ba9c]913                int port;
914
915                /* answer from proxy */
916
[5ebff60]917                if ((c = xt_find_node(c->children, "streamhost")) &&
918                    (host = xt_find_attr(c, "host")) &&
919                    (port_s = xt_find_attr(c, "port")) &&
920                    (sscanf(port_s, "%d", &port) == 1) &&
921                    (jid = xt_find_attr(c, "jid"))) {
922                        jabber_streamhost_t *sh = g_new0(jabber_streamhost_t, 1);
[dc0ba9c]923
[5ebff60]924                        sh->jid = g_strdup(jid);
925                        sh->host = g_strdup(host);
926                        g_snprintf(sh->port, sizeof(sh->port), "%u", port);
927
928                        imcb_log(ic, "Proxy found: jid %s host %s port %u", jid, host, port);
929                        jd->streamhosts = g_slist_append(jd->streamhosts, sh);
[dc0ba9c]930                }
931        }
932
[5ebff60]933        if (jd->have_streamhosts == 0) {
[dc0ba9c]934                jd->have_streamhosts++;
[5ebff60]935        }
[1c3008a]936
[dc0ba9c]937        return XT_HANDLED;
938}
[d88c92a]939
[5ebff60]940static xt_status jabber_iq_version_response(struct im_connection *ic,
941                                            struct xt_node *node, struct xt_node *orig);
[d88c92a]942
[5ebff60]943void jabber_iq_version_send(struct im_connection *ic, struct jabber_buddy *bud, void *data)
[d88c92a]944{
945        struct xt_node *node, *query;
946
[5ebff60]947        node = xt_new_node("query", NULL, NULL);
948        xt_add_attr(node, "xmlns", XMLNS_VERSION);
949        query = jabber_make_packet("iq", "get", bud->full_jid, node);
950        jabber_cache_add(ic, query, jabber_iq_version_response);
951
952        jabber_write_packet(ic, query);
[d88c92a]953}
954
[5ebff60]955static xt_status jabber_iq_version_response(struct im_connection *ic,
956                                            struct xt_node *node, struct xt_node *orig)
[d88c92a]957{
958        struct xt_node *query;
959        GString *rets;
960        char *s;
961        char *ret[2] = {};
962        bee_user_t *bu;
963        struct jabber_buddy *bud = NULL;
[5ebff60]964
965        if ((s = xt_find_attr(node, "from")) &&
966            (bud = jabber_buddy_by_jid(ic, s, 0)) &&
967            (query = xt_find_node(node->children, "query")) &&
968            (bu = bee_user_by_handle(ic->bee, ic, bud->bare_jid))) {
969                rets = g_string_new("Resource ");
970                g_string_append(rets, bud->resource);
971        } else {
[d88c92a]972                return XT_HANDLED;
[5ebff60]973        }
974
975        for (query = query->children; query; query = query->next) {
976                if (query->text_len > 0) {
977                        g_string_append_printf(rets, " %s: %s,", query->name, query->text);
978                }
979        }
980
981        g_string_truncate(rets, rets->len - 1);
[d88c92a]982        ret[0] = rets->str;
[5ebff60]983        imcb_buddy_action_response(bu, "VERSION", ret, NULL);
984        g_string_free(rets, TRUE);
985
[d88c92a]986        return XT_HANDLED;
987}
[bb2d198]988
[5ebff60]989static xt_status jabber_iq_disco_server_response(struct im_connection *ic,
990                                                 struct xt_node *node, struct xt_node *orig);
[bb2d198]991
[40cfbc5]992int jabber_iq_disco_server(struct im_connection *ic)
[bb2d198]993{
994        struct xt_node *node, *iq;
995        struct jabber_data *jd = ic->proto_data;
[5ebff60]996
997        node = xt_new_node("query", NULL, NULL);
998        xt_add_attr(node, "xmlns", XMLNS_DISCO_INFO);
999        iq = jabber_make_packet("iq", "get", jd->server, node);
1000
1001        jabber_cache_add(ic, iq, jabber_iq_disco_server_response);
1002        return jabber_write_packet(ic, iq);
[bb2d198]1003}
1004
[5ebff60]1005static xt_status jabber_iq_disco_server_response(struct im_connection *ic,
1006                                                 struct xt_node *node, struct xt_node *orig)
[bb2d198]1007{
1008        struct jabber_data *jd = ic->proto_data;
[fa8f57b]1009        struct xt_node *query, *id;
[5ebff60]1010
[fa8f57b]1011        if (!(query = xt_find_node(node->children, "query"))) {
1012                return XT_HANDLED;
1013        }
1014
1015        if (xt_find_node_by_attr(query->children, "feature", "var", XMLNS_CARBONS) &&
1016            set_getbool(&ic->acc->set, "carbons")) {
1017
1018                struct xt_node *enable, *iq;
1019
1020                enable = xt_new_node("enable", NULL, NULL);
1021                xt_add_attr(enable, "xmlns", XMLNS_CARBONS);
1022                iq = jabber_make_packet("iq", "set", NULL, enable);
1023
1024                jabber_cache_add(ic, iq, jabber_iq_carbons_response);
1025                jabber_write_packet(ic, iq);
1026        }
1027
1028        if ((id = xt_find_node(query->children, "identity"))) {
[bb2d198]1029                char *cat, *type, *name;
[5ebff60]1030
1031                if (!(cat = xt_find_attr(id, "category")) ||
1032                    !(type = xt_find_attr(id, "type")) ||
1033                    !(name = xt_find_attr(id, "name"))) {
[bb2d198]1034                        return XT_HANDLED;
[5ebff60]1035                }
1036
1037                if (strcmp(cat, "server") == 0 && strcmp(type, "im") == 0 &&
1038                    strstr(name, "Google") != NULL) {
[bb2d198]1039                        jd->flags |= JFLAG_GTALK;
[5ebff60]1040                }
[bb2d198]1041        }
[5ebff60]1042
[bb2d198]1043        return XT_HANDLED;
1044}
[fa8f57b]1045
1046static xt_status jabber_iq_carbons_response(struct im_connection *ic,
1047                                            struct xt_node *node, struct xt_node *orig)
1048{
1049        struct jabber_error *err;
1050
1051        if ((err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR))) {
1052                imcb_error(ic, "Error enabling carbons: %s%s%s",
1053                           err->code, err->text ? ": " : "", err->text ? err->text : "");
1054                jabber_error_free(err);
1055        } else {
1056                imcb_log(ic, "Carbons enabled");
1057        }
1058
1059        return XT_HANDLED;
1060}
[399d65a]1061
1062xt_status jabber_iq_disco_muc_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig);
1063
1064int jabber_iq_disco_muc(struct im_connection *ic, const char *muc_server)
1065{
1066        struct xt_node *node;
1067        int st;
1068
1069        node = xt_new_node("query", NULL, NULL);
1070        xt_add_attr(node, "xmlns", XMLNS_DISCO_ITEMS);
1071        node = jabber_make_packet("iq", "get", (char *) muc_server, node);
1072
1073        jabber_cache_add(ic, node, jabber_iq_disco_muc_response);
1074        st = jabber_write_packet(ic, node);
1075
1076        return st;
1077}
1078
1079xt_status jabber_iq_disco_muc_response(struct im_connection *ic, struct xt_node *node, struct xt_node *orig)
1080{
1081        struct xt_node *query, *c;
1082        struct jabber_error *err;
1083        GSList *rooms =  NULL;
1084
1085        if ((err = jabber_error_parse(xt_find_node(node->children, "error"), XMLNS_STANZA_ERROR))) {
1086                imcb_error(ic, "The server replied with an error: %s%s%s",
1087                           err->code, err->text ? ": " : "", err->text ? err->text : "");
1088                jabber_error_free(err);
1089                return XT_HANDLED;
1090        }
1091
1092        if (!(query = xt_find_node(node->children, "query"))) {
1093                imcb_error(ic, "Received incomplete MUC list reply");
1094                return XT_HANDLED;
1095        }
1096
1097        c = query->children;
1098        while ((c = xt_find_node(c, "item"))) {
1099                char *jid = xt_find_attr(c, "jid");
1100
1101                if (!jid || !strchr(jid, '@')) {
1102                        c = c->next;
1103                        continue;
1104                }
1105
1106                bee_chat_info_t *ci = g_new(bee_chat_info_t, 1);
1107                ci->title = g_strdup(xt_find_attr(c, "jid"));
1108                ci->topic = g_strdup(xt_find_attr(c, "name"));
1109                rooms = g_slist_prepend(rooms, ci);
1110
1111                c = c->next;
1112        }
1113
1114        imcb_chat_list_free(ic);
1115        ic->chatlist = g_slist_reverse(rooms);
1116        imcb_chat_list_finish(ic);
1117
1118        return XT_HANDLED;
1119}
Note: See TracBrowser for help on using the repository browser.