Coverage Report

Created: 2026-10-13 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/disco.c
Line
Count
Source
1
/*
2
 * purple - Jabber Service Discovery
3
 *
4
 * Purple is the legal property of its developers, whose names are too numerous
5
 * to list here.  Please refer to the COPYRIGHT file distributed with this
6
 * source distribution.
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
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
24
#include "internal.h"
25
#include "network.h"
26
#include "prefs.h"
27
#include "debug.h"
28
#include "request.h"
29
30
#include "adhoccommands.h"
31
#include "buddy.h"
32
#include "disco.h"
33
#include "google/google.h"
34
#include "google/gmail.h"
35
#include "google/jingleinfo.h"
36
#include "iq.h"
37
#include "jabber.h"
38
#include "jingle/jingle.h"
39
#include "pep.h"
40
#include "presence.h"
41
#include "roster.h"
42
#include "useravatar.h"
43
44
struct _jabber_disco_info_cb_data {
45
  gpointer data;
46
  JabberDiscoInfoCallback *callback;
47
};
48
49
struct _jabber_disco_items_cb_data {
50
  gpointer data;
51
  JabberDiscoItemsCallback *callback;
52
};
53
54
#define SUPPORT_FEATURE(x) { \
55
  feature = xmlnode_new_child(query, "feature"); \
56
  xmlnode_set_attrib(feature, "var", x); \
57
}
58
59
static void
60
jabber_disco_bytestream_server_cb(JabberStream *js, const char *from,
61
                                  JabberIqType type, const char *id,
62
                                  xmlnode *packet, gpointer data)
63
0
{
64
0
  JabberBytestreamsStreamhost *sh = data;
65
0
  xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
66
0
    NS_BYTESTREAMS);
67
68
0
  if (from && purple_strequal(from, sh->jid) && query != NULL) {
69
0
    xmlnode *sh_node = xmlnode_get_child(query, "streamhost");
70
0
    if (sh_node) {
71
0
      const char *jid = xmlnode_get_attrib(sh_node, "jid");
72
0
      const char *port = xmlnode_get_attrib(sh_node, "port");
73
74
75
0
      if (jid == NULL || !purple_strequal(jid, from))
76
0
        purple_debug_error("jabber", "Invalid jid(%s) for bytestream.\n",
77
0
               jid ? jid : "(null)");
78
79
0
      sh->host = g_strdup(xmlnode_get_attrib(sh_node, "host"));
80
0
      sh->zeroconf = g_strdup(xmlnode_get_attrib(sh_node, "zeroconf"));
81
0
      if (port != NULL)
82
0
        sh->port = atoi(port);
83
0
    }
84
0
  }
85
86
0
  purple_debug_info("jabber", "Discovered bytestream proxy server: "
87
0
        "jid='%s' host='%s' port='%d' zeroconf='%s'\n",
88
0
         from ? from : "", sh->host ? sh->host : "",
89
0
         sh->port, sh->zeroconf ? sh->zeroconf : "");
90
91
  /* TODO: When we support zeroconf proxies, fix this to handle them */
92
0
  if (!(sh->jid && sh->host && sh->port > 0)) {
93
0
    js->bs_proxies = g_list_remove(js->bs_proxies, sh);
94
0
    g_free(sh->jid);
95
0
    g_free(sh->host);
96
0
    g_free(sh->zeroconf);
97
0
    g_free(sh);
98
0
  }
99
0
}
100
101
102
void jabber_disco_info_parse(JabberStream *js, const char *from,
103
                             JabberIqType type, const char *id,
104
                             xmlnode *in_query)
105
0
{
106
0
  if(type == JABBER_IQ_GET) {
107
0
    xmlnode *query, *identity, *feature;
108
0
    JabberIq *iq;
109
0
    const char *node = xmlnode_get_attrib(in_query, "node");
110
0
    char *node_uri = NULL;
111
112
    /* create custom caps node URI */
113
0
    node_uri = g_strconcat(CAPS0115_NODE, "#", jabber_caps_get_own_hash(js), NULL);
114
115
0
    iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, NS_DISCO_INFO);
116
117
0
    jabber_iq_set_id(iq, id);
118
119
0
    if (from)
120
0
      xmlnode_set_attrib(iq->node, "to", from);
121
0
    query = xmlnode_get_child(iq->node, "query");
122
123
0
    if(node)
124
0
      xmlnode_set_attrib(query, "node", node);
125
126
0
    if(!node || purple_strequal(node, node_uri)) {
127
0
      GList *features, *identities;
128
0
      for(identities = jabber_identities; identities; identities = identities->next) {
129
0
        JabberIdentity *ident = (JabberIdentity*)identities->data;
130
0
        identity = xmlnode_new_child(query, "identity");
131
0
        xmlnode_set_attrib(identity, "category", ident->category);
132
0
        xmlnode_set_attrib(identity, "type", ident->type);
133
0
        if (ident->lang)
134
0
          xmlnode_set_attrib(identity, "xml:lang", ident->lang);
135
0
        if (ident->name)
136
0
          xmlnode_set_attrib(identity, "name", ident->name);
137
0
      }
138
0
      for(features = jabber_features; features; features = features->next) {
139
0
        JabberFeature *feat = (JabberFeature*)features->data;
140
0
        if (!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
141
0
          feature = xmlnode_new_child(query, "feature");
142
0
          xmlnode_set_attrib(feature, "var", feat->namespace);
143
0
        }
144
0
      }
145
#ifdef USE_VV
146
    } else if (purple_strequal(node, CAPS0115_NODE "#" "voice-v1")) {
147
      /*
148
       * HUGE HACK! We advertise this ext (see jabber_presence_create_js
149
       * where we add <c/> to the <presence/>) for the Google Talk
150
       * clients that don't actually check disco#info features.
151
       *
152
       * This specific feature is redundant but is what
153
       * node='http://mail.google.com/xmpp/client/caps', ver='1.1'
154
       * advertises as 'voice-v1'.
155
       */
156
      xmlnode *feature = xmlnode_new_child(query, "feature");
157
      xmlnode_set_attrib(feature, "var", NS_GOOGLE_VOICE);
158
    } else if (purple_strequal(node, CAPS0115_NODE "#" "video-v1")) {
159
      /*
160
       * HUGE HACK! We advertise this ext (see jabber_presence_create_js
161
       * where we add <c/> to the <presence/>) for the Google Talk
162
       * clients that don't actually check disco#info features.
163
       *
164
       * This specific feature is redundant but is what
165
       * node='http://mail.google.com/xmpp/client/caps', ver='1.1'
166
       * advertises as 'video-v1'.
167
       */
168
      xmlnode *feature = xmlnode_new_child(query, "feature");
169
      xmlnode_set_attrib(feature, "var", NS_GOOGLE_VIDEO);
170
    } else if (purple_strequal(node, CAPS0115_NODE "#" "camera-v1")) {
171
      /*
172
       * HUGE HACK! We advertise this ext (see jabber_presence_create_js
173
       * where we add <c/> to the <presence/>) for the Google Talk
174
       * clients that don't actually check disco#info features.
175
       *
176
       * This specific feature is redundant but is what
177
       * node='http://mail.google.com/xmpp/client/caps', ver='1.1'
178
       * advertises as 'camera-v1'.
179
       */
180
      xmlnode *feature = xmlnode_new_child(query, "feature");
181
      xmlnode_set_attrib(feature, "var", NS_GOOGLE_CAMERA);
182
#endif
183
0
    } else {
184
0
      xmlnode *error, *inf;
185
186
      /* XXX: gross hack, implement jabber_iq_set_type or something */
187
0
      xmlnode_set_attrib(iq->node, "type", "error");
188
0
      iq->type = JABBER_IQ_ERROR;
189
190
0
      error = xmlnode_new_child(query, "error");
191
0
      xmlnode_set_attrib(error, "code", "404");
192
0
      xmlnode_set_attrib(error, "type", "cancel");
193
0
      inf = xmlnode_new_child(error, "item-not-found");
194
0
      xmlnode_set_namespace(inf, NS_XMPP_STANZAS);
195
0
    }
196
0
    g_free(node_uri);
197
0
    jabber_iq_send(iq);
198
0
  } else if (type == JABBER_IQ_SET) {
199
    /* wtf? seriously. wtf‽ */
200
0
    JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR);
201
0
    xmlnode *error, *bad_request;
202
203
    /* Free the <query/> */
204
0
    xmlnode_free(xmlnode_get_child(iq->node, "query"));
205
    /* Add an error */
206
0
    error = xmlnode_new_child(iq->node, "error");
207
0
    xmlnode_set_attrib(error, "type", "modify");
208
0
    bad_request = xmlnode_new_child(error, "bad-request");
209
0
    xmlnode_set_namespace(bad_request, NS_XMPP_STANZAS);
210
211
0
    jabber_iq_set_id(iq, id);
212
0
    if (from)
213
0
      xmlnode_set_attrib(iq->node, "to", from);
214
215
0
    jabber_iq_send(iq);
216
0
  }
217
0
}
218
219
static void jabber_disco_info_cb(JabberStream *js, const char *from,
220
                                 JabberIqType type, const char *id,
221
                                 xmlnode *packet, gpointer data)
222
0
{
223
0
  struct _jabber_disco_info_cb_data *jdicd = data;
224
0
  xmlnode *query;
225
226
0
  query = xmlnode_get_child_with_namespace(packet, "query", NS_DISCO_INFO);
227
228
0
  if (type == JABBER_IQ_RESULT && query) {
229
0
    xmlnode *child;
230
0
    JabberID *jid;
231
0
    JabberBuddy *jb;
232
0
    JabberBuddyResource *jbr = NULL;
233
0
    JabberCapabilities capabilities = JABBER_CAP_NONE;
234
235
0
    if((jid = jabber_id_new(from))) {
236
0
      if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
237
0
        jbr = jabber_buddy_find_resource(jb, jid->resource);
238
0
      jabber_id_free(jid);
239
0
    }
240
241
0
    if(jbr)
242
0
      capabilities = jbr->capabilities;
243
244
0
    for(child = query->child; child; child = child->next) {
245
0
      if(child->type != XMLNODE_TYPE_TAG)
246
0
        continue;
247
248
0
      if(purple_strequal(child->name, "identity")) {
249
0
        const char *category = xmlnode_get_attrib(child, "category");
250
0
        const char *type = xmlnode_get_attrib(child, "type");
251
0
        if(!category || !type)
252
0
          continue;
253
254
0
        if(purple_strequal(category, "conference") && purple_strequal(type, "text")) {
255
          /* we found a groupchat or MUC server, add it to the list */
256
          /* XXX: actually check for protocol/muc or gc-1.0 support */
257
0
          js->chat_servers = g_list_prepend(js->chat_servers, g_strdup(from));
258
0
        } else if(purple_strequal(category, "directory") && purple_strequal(type, "user")) {
259
          /* we found a JUD */
260
0
          js->user_directories = g_list_prepend(js->user_directories, g_strdup(from));
261
0
        } else if(purple_strequal(category, "proxy") && purple_strequal(type, "bytestreams")) {
262
          /* This is a bytestream proxy */
263
0
          JabberIq *iq;
264
0
          JabberBytestreamsStreamhost *sh;
265
266
0
          purple_debug_info("jabber", "Found bytestream proxy server: %s\n", from);
267
268
0
          sh = g_new0(JabberBytestreamsStreamhost, 1);
269
0
          sh->jid = g_strdup(from);
270
0
          js->bs_proxies = g_list_prepend(js->bs_proxies, sh);
271
272
0
          iq = jabber_iq_new_query(js, JABBER_IQ_GET,
273
0
              NS_BYTESTREAMS);
274
0
          xmlnode_set_attrib(iq->node, "to", sh->jid);
275
0
          jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh);
276
0
          jabber_iq_send(iq);
277
0
        }
278
279
0
      } else if(purple_strequal(child->name, "feature")) {
280
0
        const char *var = xmlnode_get_attrib(child, "var");
281
0
        if(!var)
282
0
          continue;
283
284
0
        if(purple_strequal(var, "http://jabber.org/protocol/si"))
285
0
          capabilities |= JABBER_CAP_SI;
286
0
        else if(purple_strequal(var, "http://jabber.org/protocol/si/profile/file-transfer"))
287
0
          capabilities |= JABBER_CAP_SI_FILE_XFER;
288
0
        else if(purple_strequal(var, NS_BYTESTREAMS))
289
0
          capabilities |= JABBER_CAP_BYTESTREAMS;
290
0
        else if(purple_strequal(var, "jabber:iq:search"))
291
0
          capabilities |= JABBER_CAP_IQ_SEARCH;
292
0
        else if(purple_strequal(var, "jabber:iq:register"))
293
0
          capabilities |= JABBER_CAP_IQ_REGISTER;
294
0
        else if(purple_strequal(var, NS_PING))
295
0
          capabilities |= JABBER_CAP_PING;
296
0
        else if(purple_strequal(var, NS_DISCO_ITEMS))
297
0
          capabilities |= JABBER_CAP_ITEMS;
298
0
        else if(purple_strequal(var, "http://jabber.org/protocol/commands")) {
299
0
          capabilities |= JABBER_CAP_ADHOC;
300
0
        }
301
0
        else if(purple_strequal(var, NS_IBB)) {
302
0
          purple_debug_info("jabber", "remote supports IBB\n");
303
0
          capabilities |= JABBER_CAP_IBB;
304
0
        }
305
0
      }
306
0
    }
307
308
0
    js->chat_servers = g_list_reverse(js->chat_servers);
309
310
0
    capabilities |= JABBER_CAP_RETRIEVED;
311
312
0
    if(jbr)
313
0
      jbr->capabilities = capabilities;
314
315
0
    if (jdicd && jdicd->callback)
316
0
      jdicd->callback(js, from, capabilities, jdicd->data);
317
0
  } else { /* type == JABBER_IQ_ERROR or query == NULL */
318
0
    JabberID *jid;
319
0
    JabberBuddy *jb;
320
0
    JabberBuddyResource *jbr = NULL;
321
0
    JabberCapabilities capabilities = JABBER_CAP_NONE;
322
323
0
    if((jid = jabber_id_new(from))) {
324
0
      if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
325
0
        jbr = jabber_buddy_find_resource(jb, jid->resource);
326
0
      jabber_id_free(jid);
327
0
    }
328
329
0
    if(jbr)
330
0
      capabilities = jbr->capabilities;
331
332
0
    if (jdicd && jdicd->callback)
333
0
      jdicd->callback(js, from, capabilities, jdicd->data);
334
0
  }
335
336
0
  g_free(jdicd);
337
0
}
338
339
void jabber_disco_items_parse(JabberStream *js, const char *from,
340
                              JabberIqType type, const char *id,
341
                              xmlnode *query)
342
0
{
343
0
  if(type == JABBER_IQ_GET) {
344
0
    JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
345
0
        NS_DISCO_ITEMS);
346
347
    /* preserve node */
348
0
    xmlnode *iq_query = xmlnode_get_child(iq->node, "query");
349
0
    const char *node = xmlnode_get_attrib(query, "node");
350
0
    if(node)
351
0
      xmlnode_set_attrib(iq_query,"node",node);
352
353
0
    jabber_iq_set_id(iq, id);
354
355
0
    if (from)
356
0
      xmlnode_set_attrib(iq->node, "to", from);
357
0
    jabber_iq_send(iq);
358
0
  }
359
0
}
360
361
static void
362
jabber_disco_finish_server_info_result_cb(JabberStream *js)
363
0
{
364
0
  const char *ft_proxies;
365
366
  /*
367
   * This *should* happen only if the server supports vcard-temp, but there
368
   * are apparently some servers that don't advertise it even though they
369
   * support it.
370
   */
371
0
  jabber_vcard_fetch_mine(js);
372
373
0
  if (js->pep)
374
0
    jabber_avatar_fetch_mine(js);
375
376
  /* Yes, please! */
377
0
  jabber_roster_request(js);
378
379
0
  if (js->server_caps & JABBER_CAP_ADHOC) {
380
    /* The server supports ad-hoc commands, so let's request the list */
381
0
    jabber_adhoc_server_get_list(js);
382
0
  }
383
384
  /* If the server supports blocking, request the block list */
385
0
  if (js->server_caps & JABBER_CAP_BLOCKING) {
386
0
    jabber_request_block_list(js);
387
0
  }
388
389
  /* If there are manually specified bytestream proxies, query them */
390
0
  ft_proxies = purple_account_get_string(js->gc->account, "ft_proxies", NULL);
391
0
  if (ft_proxies) {
392
0
    JabberIq *iq;
393
0
    JabberBytestreamsStreamhost *sh;
394
0
    int i;
395
0
    char *tmp;
396
0
    gchar **ft_proxy_list = g_strsplit(ft_proxies, ",", 0);
397
398
0
    for(i = 0; ft_proxy_list[i]; i++) {
399
0
      g_strstrip(ft_proxy_list[i]);
400
0
      if(!(*ft_proxy_list[i]))
401
0
        continue;
402
403
      /* We used to allow specifying a port directly here; get rid of it */
404
0
      if((tmp = strchr(ft_proxy_list[i], ':')))
405
0
        *tmp = '\0';
406
407
0
      sh = g_new0(JabberBytestreamsStreamhost, 1);
408
0
      sh->jid = g_strdup(ft_proxy_list[i]);
409
0
      js->bs_proxies = g_list_prepend(js->bs_proxies, sh);
410
411
0
      iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_BYTESTREAMS);
412
0
      xmlnode_set_attrib(iq->node, "to", sh->jid);
413
0
      jabber_iq_set_callback(iq, jabber_disco_bytestream_server_cb, sh);
414
0
      jabber_iq_send(iq);
415
0
    }
416
417
0
    g_strfreev(ft_proxy_list);
418
0
  }
419
420
0
}
421
422
/* should probably share this code with google.c, or maybe from 2.7.0
423
 introduce an abstracted hostname -> IP function in dns.c */
424
static void
425
jabber_disco_stun_lookup_cb(GSList *hosts, gpointer data,
426
  const char *error_message)
427
0
{
428
0
  JabberStream *js = (JabberStream *) data;
429
430
0
  if (error_message) {
431
0
    purple_debug_error("jabber", "STUN lookup failed: %s\n",
432
0
      error_message);
433
0
    g_slist_free(hosts);
434
0
    js->stun_query = NULL;
435
0
    return;
436
0
  }
437
438
0
  if (hosts && g_slist_next(hosts)) {
439
0
    struct sockaddr *addr = g_slist_next(hosts)->data;
440
0
    char dst[INET6_ADDRSTRLEN];
441
0
    int port;
442
443
0
    if (addr->sa_family == AF_INET6) {
444
0
      inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr,
445
0
        dst, sizeof(dst));
446
0
      port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
447
0
    } else {
448
0
      inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr,
449
0
        dst, sizeof(dst));
450
0
      port = ntohs(((struct sockaddr_in *) addr)->sin_port);
451
0
    }
452
453
0
    if (js->stun_ip)
454
0
      g_free(js->stun_ip);
455
0
    js->stun_ip = g_strdup(dst);
456
0
    js->stun_port = port;
457
458
0
    purple_debug_info("jabber", "set STUN IP/port address: "
459
0
                      "%s:%d\n", dst, port);
460
461
    /* unmark ongoing query */
462
0
    js->stun_query = NULL;
463
0
  }
464
465
0
  while (hosts != NULL) {
466
0
    hosts = g_slist_delete_link(hosts, hosts);
467
    /* Free the address */
468
0
    g_free(hosts->data);
469
0
    hosts = g_slist_delete_link(hosts, hosts);
470
0
  }
471
0
}
472
473
474
static void
475
jabber_disco_stun_srv_resolve_cb(PurpleSrvResponse *resp, int results, gpointer data)
476
0
{
477
0
  JabberStream *js = (JabberStream *) data;
478
479
0
  purple_debug_info("jabber", "got %d SRV responses for STUN.\n", results);
480
0
  js->srv_query_data = NULL;
481
482
0
  if (results > 0) {
483
0
    PurpleAccount *account;
484
0
    purple_debug_info("jabber", "looking up IP for %s:%d\n",
485
0
      resp[0].hostname, resp[0].port);
486
0
    account = purple_connection_get_account(js->gc);
487
0
    js->stun_query =
488
0
      purple_dnsquery_a_account(account, resp[0].hostname, resp[0].port,
489
0
        jabber_disco_stun_lookup_cb, js);
490
0
  }
491
0
}
492
493
494
static void
495
jabber_disco_server_info_result_cb(JabberStream *js, const char *from,
496
                                   JabberIqType type, const char *id,
497
                                   xmlnode *packet, gpointer data)
498
0
{
499
0
  xmlnode *query, *child;
500
501
0
  if (!from || !purple_strequal(from, js->user->domain)) {
502
0
    jabber_disco_finish_server_info_result_cb(js);
503
0
    return;
504
0
  }
505
506
0
  if (type == JABBER_IQ_ERROR) {
507
    /* A common way to get here is for the server not to support xmlns http://jabber.org/protocol/disco#info */
508
0
    jabber_disco_finish_server_info_result_cb(js);
509
0
    return;
510
0
  }
511
512
0
  query = xmlnode_get_child(packet, "query");
513
514
0
  if (!query) {
515
0
    jabber_disco_finish_server_info_result_cb(js);
516
0
    return;
517
0
  }
518
519
0
  for (child = xmlnode_get_child(query, "identity"); child;
520
0
       child = xmlnode_get_next_twin(child)) {
521
0
    const char *category, *type, *name;
522
0
    const char *stun_ip;
523
0
    category = xmlnode_get_attrib(child, "category");
524
0
    type = xmlnode_get_attrib(child, "type");
525
0
    if(purple_strequal(category, "pubsub") && purple_strequal(type, "pep")) {
526
0
      PurpleConnection *gc = js->gc;
527
0
      js->pep = TRUE;
528
0
      gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS |
529
0
        PURPLE_CONNECTION_SUPPORT_MOOD_MESSAGES;
530
0
    }
531
0
    if (!purple_strequal(category, "server"))
532
0
      continue;
533
0
    if (!purple_strequal(type, "im"))
534
0
      continue;
535
536
0
    name = xmlnode_get_attrib(child, "name");
537
0
    if (!name)
538
0
      continue;
539
540
0
    g_free(js->server_name);
541
0
    js->server_name = g_strdup(name);
542
0
    stun_ip = purple_network_get_stun_ip();
543
0
    if (purple_strequal(name, "Google Talk")) {
544
0
      purple_debug_info("jabber", "Google Talk!\n");
545
0
      js->googletalk = TRUE;
546
547
      /* autodiscover stun and relays */
548
0
      if (!stun_ip || !*stun_ip) {
549
0
        jabber_google_send_jingle_info(js);
550
0
      }
551
0
    } else if (!stun_ip || !*stun_ip) {
552
0
      js->srv_query_data =
553
0
        purple_srv_resolve_account(
554
0
          purple_connection_get_account(js->gc), "stun", "udp",
555
0
          js->user->domain,
556
0
          jabber_disco_stun_srv_resolve_cb, js);
557
      /* TODO: add TURN support later... */
558
0
    }
559
0
  }
560
561
0
  for (child = xmlnode_get_child(query, "feature"); child;
562
0
       child = xmlnode_get_next_twin(child)) {
563
0
    const char *var;
564
0
    var = xmlnode_get_attrib(child, "var");
565
0
    if (!var)
566
0
      continue;
567
568
0
    if (purple_strequal(NS_GOOGLE_MAIL_NOTIFY, var)) {
569
0
      js->server_caps |= JABBER_CAP_GMAIL_NOTIFY;
570
0
      jabber_gmail_init(js);
571
0
    } else if (purple_strequal(NS_GOOGLE_ROSTER, var)) {
572
0
      js->server_caps |= JABBER_CAP_GOOGLE_ROSTER;
573
0
    } else if (purple_strequal("http://jabber.org/protocol/commands", var)) {
574
0
      js->server_caps |= JABBER_CAP_ADHOC;
575
0
    } else if (purple_strequal(NS_SIMPLE_BLOCKING, var)) {
576
0
      js->server_caps |= JABBER_CAP_BLOCKING;
577
0
    }
578
0
  }
579
580
0
  jabber_disco_finish_server_info_result_cb(js);
581
0
}
582
583
static void
584
jabber_disco_server_items_result_cb(JabberStream *js, const char *from,
585
                                    JabberIqType type, const char *id,
586
                                    xmlnode *packet, gpointer data)
587
0
{
588
0
  xmlnode *query, *child;
589
590
0
  if (!from || !purple_strequal(from, js->user->domain))
591
0
    return;
592
593
0
  if (type == JABBER_IQ_ERROR)
594
0
    return;
595
596
0
  while(js->chat_servers) {
597
0
    g_free(js->chat_servers->data);
598
0
    js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers);
599
0
  }
600
601
0
  query = xmlnode_get_child(packet, "query");
602
603
0
  for(child = xmlnode_get_child(query, "item"); child;
604
0
      child = xmlnode_get_next_twin(child)) {
605
0
    JabberIq *iq;
606
0
    const char *jid;
607
608
0
    if(!(jid = xmlnode_get_attrib(child, "jid")))
609
0
      continue;
610
611
    /* we don't actually care about the specific nodes,
612
     * so we won't query them */
613
0
    if(xmlnode_get_attrib(child, "node") != NULL)
614
0
      continue;
615
616
0
    iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
617
0
    xmlnode_set_attrib(iq->node, "to", jid);
618
0
    jabber_iq_set_callback(iq, jabber_disco_info_cb, NULL);
619
0
    jabber_iq_send(iq);
620
0
  }
621
0
}
622
623
void jabber_disco_items_server(JabberStream *js)
624
0
{
625
0
  JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_ITEMS);
626
627
0
  xmlnode_set_attrib(iq->node, "to", js->user->domain);
628
629
0
  jabber_iq_set_callback(iq, jabber_disco_server_items_result_cb, NULL);
630
0
  jabber_iq_send(iq);
631
632
0
  iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
633
0
  xmlnode_set_attrib(iq->node, "to", js->user->domain);
634
0
  jabber_iq_set_callback(iq, jabber_disco_server_info_result_cb, NULL);
635
0
  jabber_iq_send(iq);
636
0
}
637
638
void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data)
639
0
{
640
0
  JabberID *jid;
641
0
  JabberBuddy *jb;
642
0
  JabberBuddyResource *jbr = NULL;
643
0
  struct _jabber_disco_info_cb_data *jdicd;
644
0
  JabberIq *iq;
645
646
0
  if((jid = jabber_id_new(who))) {
647
0
    if(jid->resource && (jb = jabber_buddy_find(js, who, TRUE)))
648
0
      jbr = jabber_buddy_find_resource(jb, jid->resource);
649
0
    jabber_id_free(jid);
650
0
  }
651
652
0
  if(jbr && jbr->capabilities & JABBER_CAP_RETRIEVED) {
653
0
    callback(js, who, jbr->capabilities, data);
654
0
    return;
655
0
  }
656
657
0
  jdicd = g_new0(struct _jabber_disco_info_cb_data, 1);
658
0
  jdicd->data = data;
659
0
  jdicd->callback = callback;
660
661
0
  iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
662
0
  xmlnode_set_attrib(iq->node, "to", who);
663
664
0
  jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd);
665
0
  jabber_iq_send(iq);
666
0
}
667