Coverage Report

Created: 2026-10-28 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/caps.c
Line
Count
Source
1
/*
2
 * purple - Jabber Protocol Plugin
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
26
#include "debug.h"
27
#include "caps.h"
28
#include "cipher.h"
29
#include "iq.h"
30
#include "presence.h"
31
#include "util.h"
32
#include "xdata.h"
33
34
0
#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
35
36
typedef struct _JabberDataFormField {
37
  gchar *var;
38
  GList *values;
39
} JabberDataFormField;
40
41
static GHashTable *capstable = NULL; /* JabberCapsTuple -> JabberCapsClientInfo */
42
static GHashTable *nodetable = NULL; /* char *node -> JabberCapsNodeExts */
43
static guint       save_timer = 0;
44
45
/* Free a GList of allocated char* */
46
static void
47
free_string_glist(GList *list)
48
0
{
49
0
  while (list) {
50
0
    g_free(list->data);
51
0
    list = g_list_delete_link(list, list);
52
0
  }
53
0
}
54
55
static JabberCapsNodeExts*
56
jabber_caps_node_exts_ref(JabberCapsNodeExts *exts)
57
0
{
58
0
  g_return_val_if_fail(exts != NULL, NULL);
59
60
0
  ++exts->ref;
61
0
  return exts;
62
0
}
63
64
static void
65
jabber_caps_node_exts_unref(JabberCapsNodeExts *exts)
66
0
{
67
0
  if (exts == NULL)
68
0
    return;
69
70
0
  g_return_if_fail(exts->ref != 0);
71
72
0
  if (--exts->ref != 0)
73
0
    return;
74
75
0
  g_hash_table_destroy(exts->exts);
76
0
  g_free(exts);
77
0
}
78
79
0
static guint jabber_caps_hash(gconstpointer data) {
80
0
  const JabberCapsTuple *key = data;
81
0
  guint nodehash = g_str_hash(key->node);
82
0
  guint verhash  = g_str_hash(key->ver);
83
  /*
84
   * 'hash' was optional in XEP-0115 v1.4 and g_str_hash crashes on NULL >:O.
85
   * Okay, maybe I've played too much Zelda, but that looks like
86
   * a Deku Shrub...
87
   */
88
0
  guint hashhash = (key->hash ? g_str_hash(key->hash) : 0);
89
0
  return nodehash ^ verhash ^ hashhash;
90
0
}
91
92
0
static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
93
0
  const JabberCapsTuple *name1 = v1;
94
0
  const JabberCapsTuple *name2 = v2;
95
96
0
  return purple_strequal(name1->node, name2->node) &&
97
0
         purple_strequal(name1->ver, name2->ver) &&
98
0
         purple_strequal(name1->hash, name2->hash);
99
0
}
100
101
static void
102
jabber_caps_client_info_destroy(JabberCapsClientInfo *info)
103
0
{
104
0
  if (info == NULL)
105
0
    return;
106
107
0
  while(info->identities) {
108
0
    JabberIdentity *id = info->identities->data;
109
0
    g_free(id->category);
110
0
    g_free(id->type);
111
0
    g_free(id->name);
112
0
    g_free(id->lang);
113
0
    g_free(id);
114
0
    info->identities = g_list_delete_link(info->identities, info->identities);
115
0
  }
116
117
0
  free_string_glist(info->features);
118
119
0
  while (info->forms) {
120
0
    xmlnode_free(info->forms->data);
121
0
    info->forms = g_list_delete_link(info->forms, info->forms);
122
0
  }
123
124
0
  jabber_caps_node_exts_unref(info->exts);
125
126
0
  g_free((char *)info->tuple.node);
127
0
  g_free((char *)info->tuple.ver);
128
0
  g_free((char *)info->tuple.hash);
129
130
0
  g_free(info);
131
0
}
132
133
/* NOTE: Takes a reference to the exts, unref it if you don't really want to
134
 * keep it around. */
135
static JabberCapsNodeExts*
136
jabber_caps_find_exts_by_node(const char *node)
137
0
{
138
0
  JabberCapsNodeExts *exts;
139
0
  if (NULL == (exts = g_hash_table_lookup(nodetable, node))) {
140
0
    exts = g_new0(JabberCapsNodeExts, 1);
141
0
    exts->exts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
142
0
                                       (GDestroyNotify)free_string_glist);
143
0
    g_hash_table_insert(nodetable, g_strdup(node), jabber_caps_node_exts_ref(exts));
144
0
  }
145
146
0
  return jabber_caps_node_exts_ref(exts);
147
0
}
148
149
static void
150
exts_to_xmlnode(gconstpointer key, gconstpointer value, gpointer user_data)
151
0
{
152
0
  const char *identifier = key;
153
0
  const GList *features = value, *node;
154
0
  xmlnode *client = user_data, *ext, *feature;
155
156
0
  ext = xmlnode_new_child(client, "ext");
157
0
  xmlnode_set_attrib(ext, "identifier", identifier);
158
159
0
  for (node = features; node; node = node->next) {
160
0
    feature = xmlnode_new_child(ext, "feature");
161
0
    xmlnode_set_attrib(feature, "var", (const gchar *)node->data);
162
0
  }
163
0
}
164
165
0
static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
166
0
  const JabberCapsTuple *tuple = key;
167
0
  const JabberCapsClientInfo *props = value;
168
0
  xmlnode *root = user_data;
169
0
  xmlnode *client = xmlnode_new_child(root, "client");
170
0
  GList *iter;
171
172
0
  xmlnode_set_attrib(client, "node", tuple->node);
173
0
  xmlnode_set_attrib(client, "ver", tuple->ver);
174
0
  if (tuple->hash)
175
0
    xmlnode_set_attrib(client, "hash", tuple->hash);
176
0
  for(iter = props->identities; iter; iter = g_list_next(iter)) {
177
0
    JabberIdentity *id = iter->data;
178
0
    xmlnode *identity = xmlnode_new_child(client, "identity");
179
0
    xmlnode_set_attrib(identity, "category", id->category);
180
0
    xmlnode_set_attrib(identity, "type", id->type);
181
0
    if (id->name)
182
0
      xmlnode_set_attrib(identity, "name", id->name);
183
0
    if (id->lang)
184
0
      xmlnode_set_attrib(identity, "lang", id->lang);
185
0
  }
186
187
0
  for(iter = props->features; iter; iter = g_list_next(iter)) {
188
0
    const char *feat = iter->data;
189
0
    xmlnode *feature = xmlnode_new_child(client, "feature");
190
0
    xmlnode_set_attrib(feature, "var", feat);
191
0
  }
192
193
0
  for(iter = props->forms; iter; iter = g_list_next(iter)) {
194
    /* FIXME: See #7814 */
195
0
    xmlnode *xdata = iter->data;
196
0
    xmlnode_insert_child(client, xmlnode_copy(xdata));
197
0
  }
198
199
  /* TODO: Ideally, only save this once-per-node... */
200
0
  if (props->exts)
201
0
    g_hash_table_foreach(props->exts->exts, (GHFunc)exts_to_xmlnode, client);
202
0
}
203
204
static gboolean
205
do_jabber_caps_store(gpointer data)
206
0
{
207
0
  char *str;
208
0
  int length = 0;
209
0
  xmlnode *root = xmlnode_new("capabilities");
210
0
  g_hash_table_foreach(capstable, jabber_caps_store_client, root);
211
0
  str = xmlnode_to_formatted_str(root, &length);
212
0
  xmlnode_free(root);
213
0
  purple_util_write_data_to_file(JABBER_CAPS_FILENAME, str, length);
214
0
  g_free(str);
215
216
0
  save_timer = 0;
217
0
  return FALSE;
218
0
}
219
220
static void
221
schedule_caps_save(void)
222
0
{
223
0
  if (save_timer == 0)
224
0
    save_timer = purple_timeout_add_seconds(5, do_jabber_caps_store, NULL);
225
0
}
226
227
static void
228
jabber_caps_load(void)
229
0
{
230
0
  xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
231
0
  xmlnode *client;
232
233
0
  if(!capsdata)
234
0
    return;
235
236
0
  if (!purple_strequal(capsdata->name, "capabilities")) {
237
0
    xmlnode_free(capsdata);
238
0
    return;
239
0
  }
240
241
0
  for (client = capsdata->child; client; client = client->next) {
242
0
    if (client->type != XMLNODE_TYPE_TAG)
243
0
      continue;
244
0
    if (purple_strequal(client->name, "client")) {
245
0
      JabberCapsClientInfo *value = g_new0(JabberCapsClientInfo, 1);
246
0
      JabberCapsTuple *key = (JabberCapsTuple*)&value->tuple;
247
0
      xmlnode *child;
248
0
      JabberCapsNodeExts *exts = NULL;
249
0
      key->node = g_strdup(xmlnode_get_attrib(client,"node"));
250
0
      key->ver  = g_strdup(xmlnode_get_attrib(client,"ver"));
251
0
      key->hash = g_strdup(xmlnode_get_attrib(client,"hash"));
252
253
      /* v1.3 capabilities */
254
0
      if (key->hash == NULL)
255
0
        exts = jabber_caps_find_exts_by_node(key->node);
256
257
0
      for (child = client->child; child; child = child->next) {
258
0
        if (child->type != XMLNODE_TYPE_TAG)
259
0
          continue;
260
0
        if (purple_strequal(child->name, "feature")) {
261
0
          const char *var = xmlnode_get_attrib(child, "var");
262
0
          if(!var)
263
0
            continue;
264
0
          value->features = g_list_append(value->features,g_strdup(var));
265
0
        } else if (purple_strequal(child->name, "identity")) {
266
0
          const char *category = xmlnode_get_attrib(child, "category");
267
0
          const char *type = xmlnode_get_attrib(child, "type");
268
0
          const char *name = xmlnode_get_attrib(child, "name");
269
0
          const char *lang = xmlnode_get_attrib(child, "lang");
270
0
          JabberIdentity *id;
271
272
0
          if (!category || !type)
273
0
            continue;
274
275
0
          id = g_new0(JabberIdentity, 1);
276
0
          id->category = g_strdup(category);
277
0
          id->type = g_strdup(type);
278
0
          id->name = g_strdup(name);
279
0
          id->lang = g_strdup(lang);
280
281
0
          value->identities = g_list_append(value->identities,id);
282
0
        } else if (purple_strequal(child->name, "x")) {
283
          /* TODO: See #7814 -- this might cause problems if anyone
284
           * ever actually specifies forms. In fact, for this to
285
           * work properly, that bug needs to be fixed in
286
           * xmlnode_from_str, not the output version... */
287
0
          value->forms = g_list_append(value->forms, xmlnode_copy(child));
288
0
        } else if (purple_strequal(child->name, "ext")) {
289
0
          if (key->hash != NULL)
290
0
            purple_debug_warning("jabber", "Ignoring exts when reading new-style caps\n");
291
0
          else {
292
            /* TODO: Do we care about reading in the identities listed here? */
293
0
            const char *identifier = xmlnode_get_attrib(child, "identifier");
294
0
            xmlnode *node;
295
0
            GList *features = NULL;
296
297
0
            if (!identifier)
298
0
              continue;
299
300
0
            for (node = child->child; node; node = node->next) {
301
0
              if (node->type != XMLNODE_TYPE_TAG)
302
0
                continue;
303
0
              if (purple_strequal(node->name, "feature")) {
304
0
                const char *var = xmlnode_get_attrib(node, "var");
305
0
                if (!var)
306
0
                  continue;
307
0
                features = g_list_prepend(features, g_strdup(var));
308
0
              }
309
0
            }
310
311
0
            if (features) {
312
0
              g_hash_table_insert(exts->exts, g_strdup(identifier),
313
0
                                  features);
314
0
            } else
315
0
              purple_debug_warning("jabber", "Caps ext %s had no features.\n",
316
0
                                   identifier);
317
0
          }
318
0
        }
319
0
      }
320
321
0
      value->exts = exts;
322
0
      g_hash_table_replace(capstable, key, value);
323
324
0
    }
325
0
  }
326
0
  xmlnode_free(capsdata);
327
0
}
328
329
void jabber_caps_init(void)
330
0
{
331
0
  nodetable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_caps_node_exts_unref);
332
0
  capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, NULL, (GDestroyNotify)jabber_caps_client_info_destroy);
333
0
  jabber_caps_load();
334
0
}
335
336
void jabber_caps_uninit(void)
337
0
{
338
0
  if (save_timer != 0) {
339
0
    purple_timeout_remove(save_timer);
340
0
    save_timer = 0;
341
0
    do_jabber_caps_store(NULL);
342
0
  }
343
0
  g_hash_table_destroy(capstable);
344
0
  g_hash_table_destroy(nodetable);
345
0
  capstable = nodetable = NULL;
346
0
}
347
348
gboolean jabber_caps_exts_known(const JabberCapsClientInfo *info,
349
                                char **exts)
350
0
{
351
0
  int i;
352
0
  g_return_val_if_fail(info != NULL, FALSE);
353
354
0
  if (!exts)
355
0
    return TRUE;
356
357
0
  for (i = 0; exts[i]; ++i) {
358
    /* Hack since we advertise the ext along with v1.5 caps but don't
359
     * store any exts */
360
0
    if (purple_strequal(exts[i], "voice-v1") && !info->exts)
361
0
      continue;
362
0
    if (!info->exts ||
363
0
        !g_hash_table_lookup(info->exts->exts, exts[i]))
364
0
      return FALSE;
365
0
  }
366
367
0
  return TRUE;
368
0
}
369
370
typedef struct _jabber_caps_cbplususerdata {
371
  guint ref;
372
373
  jabber_caps_get_info_cb cb;
374
  gpointer cb_data;
375
376
  char *who;
377
  char *node;
378
  char *ver;
379
  char *hash;
380
381
  JabberCapsClientInfo *info;
382
383
  GList *exts;
384
  guint extOutstanding;
385
  JabberCapsNodeExts *node_exts;
386
} jabber_caps_cbplususerdata;
387
388
static jabber_caps_cbplususerdata*
389
cbplususerdata_ref(jabber_caps_cbplususerdata *data)
390
0
{
391
0
  g_return_val_if_fail(data != NULL, NULL);
392
393
0
  ++data->ref;
394
0
  return data;
395
0
}
396
397
static void
398
cbplususerdata_unref(jabber_caps_cbplususerdata *data)
399
0
{
400
0
  if (data == NULL)
401
0
    return;
402
403
0
  g_return_if_fail(data->ref != 0);
404
405
0
  if (--data->ref > 0)
406
0
    return;
407
408
0
  g_free(data->who);
409
0
  g_free(data->node);
410
0
  g_free(data->ver);
411
0
  g_free(data->hash);
412
413
  /* If we have info here, it's already in the capstable, so don't free it */
414
0
  if (data->exts)
415
0
    free_string_glist(data->exts);
416
0
  if (data->node_exts)
417
0
    jabber_caps_node_exts_unref(data->node_exts);
418
0
  g_free(data);
419
0
}
420
421
static void
422
jabber_caps_get_info_complete(jabber_caps_cbplususerdata *userdata)
423
0
{
424
0
  if (userdata->cb) {
425
0
    userdata->cb(userdata->info, userdata->exts, userdata->cb_data);
426
0
    userdata->info = NULL;
427
0
    userdata->exts = NULL;
428
0
  }
429
430
0
  if (userdata->ref != 1)
431
0
    purple_debug_warning("jabber", "Lost a reference to caps cbdata: %d\n",
432
0
                         userdata->ref);
433
0
}
434
435
static void
436
jabber_caps_client_iqcb(JabberStream *js, const char *from, JabberIqType type,
437
                        const char *id, xmlnode *packet, gpointer data)
438
0
{
439
0
  xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
440
0
    NS_DISCO_INFO);
441
0
  jabber_caps_cbplususerdata *userdata = data;
442
0
  JabberCapsClientInfo *info = NULL, *value;
443
0
  JabberCapsTuple key;
444
445
0
  if (!query || type == JABBER_IQ_ERROR) {
446
    /* Any outstanding exts will be dealt with via ref-counting */
447
0
    userdata->cb(NULL, NULL, userdata->cb_data);
448
0
    cbplususerdata_unref(userdata);
449
0
    return;
450
0
  }
451
452
  /* check hash */
453
0
  info = jabber_caps_parse_client_info(query);
454
455
  /* Only validate if these are v1.5 capabilities */
456
0
  if (userdata->hash) {
457
0
    gchar *hash = NULL;
458
    /*
459
     * TODO: If you add *any* hash here, make sure the checksum buffer
460
     * size in jabber_caps_calculate_hash is large enough. The cipher API
461
     * doesn't seem to offer a "Get the hash size" function(?).
462
     */
463
0
    if (purple_strequal(userdata->hash, "sha-1")) {
464
0
      hash = jabber_caps_calculate_hash(info, "sha1");
465
0
    } else if (purple_strequal(userdata->hash, "md5")) {
466
0
      hash = jabber_caps_calculate_hash(info, "md5");
467
0
    }
468
469
0
    if (!hash || !purple_strequal(hash, userdata->ver)) {
470
0
      purple_debug_warning("jabber", "Could not validate caps info from "
471
0
                           "%s. Expected %s, got %s\n",
472
0
                           xmlnode_get_attrib(packet, "from"),
473
0
                           userdata->ver, hash ? hash : "(null)");
474
475
0
      userdata->cb(NULL, NULL, userdata->cb_data);
476
0
      jabber_caps_client_info_destroy(info);
477
0
      cbplususerdata_unref(userdata);
478
0
      g_free(hash);
479
0
      return;
480
0
    }
481
482
0
    g_free(hash);
483
0
  }
484
485
0
  if (!userdata->hash && userdata->node_exts) {
486
    /* If the ClientInfo doesn't have information about the exts, give them
487
     * ours (along with our ref) */
488
0
    info->exts = userdata->node_exts;
489
0
    userdata->node_exts = NULL;
490
0
  }
491
492
0
  key.node = userdata->node;
493
0
  key.ver  = userdata->ver;
494
0
  key.hash = userdata->hash;
495
496
  /* Use the copy of this data already in the table if it exists or insert
497
   * a new one if we need to */
498
0
  if ((value = g_hash_table_lookup(capstable, &key))) {
499
0
    jabber_caps_client_info_destroy(info);
500
0
    info = value;
501
0
  } else {
502
0
    JabberCapsTuple *n_key = (JabberCapsTuple *)&info->tuple;
503
0
    n_key->node = userdata->node;
504
0
    n_key->ver  = userdata->ver;
505
0
    n_key->hash = userdata->hash;
506
0
    userdata->node = userdata->ver = userdata->hash = NULL;
507
508
    /* The capstable gets a reference */
509
0
    g_hash_table_insert(capstable, n_key, info);
510
0
    schedule_caps_save();
511
0
  }
512
513
0
  userdata->info = info;
514
515
0
  if (userdata->extOutstanding == 0)
516
0
    jabber_caps_get_info_complete(userdata);
517
518
0
  cbplususerdata_unref(userdata);
519
0
}
520
521
typedef struct {
522
  const char *name;
523
  jabber_caps_cbplususerdata *data;
524
} ext_iq_data;
525
526
static void
527
jabber_caps_ext_iqcb(JabberStream *js, const char *from, JabberIqType type,
528
                     const char *id, xmlnode *packet, gpointer data)
529
0
{
530
0
  xmlnode *query = xmlnode_get_child_with_namespace(packet, "query",
531
0
    NS_DISCO_INFO);
532
0
  xmlnode *child;
533
0
  ext_iq_data *userdata = data;
534
0
  GList *features = NULL;
535
0
  JabberCapsNodeExts *node_exts;
536
537
0
  if (!query || type == JABBER_IQ_ERROR) {
538
0
    cbplususerdata_unref(userdata->data);
539
0
    g_free(userdata);
540
0
    return;
541
0
  }
542
543
0
  node_exts = (userdata->data->info ? userdata->data->info->exts :
544
0
                                      userdata->data->node_exts);
545
546
  /* TODO: I don't see how this can actually happen, but it crashed khc. */
547
0
  if (!node_exts) {
548
0
    purple_debug_error("jabber", "Couldn't find JabberCapsNodeExts. If you "
549
0
        "see this, please tell darkrain42 and save your debug log.\n"
550
0
        "JabberCapsClientInfo = %p\n", userdata->data->info);
551
552
553
    /* Try once more to find the exts and then fail */
554
0
    node_exts = jabber_caps_find_exts_by_node(userdata->data->node);
555
0
    if (node_exts) {
556
0
      purple_debug_info("jabber", "Found the exts on the second try.\n");
557
0
      if (userdata->data->info)
558
0
        userdata->data->info->exts = node_exts;
559
0
      else
560
0
        userdata->data->node_exts = node_exts;
561
0
    } else {
562
0
      cbplususerdata_unref(userdata->data);
563
0
      g_free(userdata);
564
0
      g_return_if_reached();
565
0
    }
566
0
  }
567
568
  /* So, we decrement this after checking for an error, which means that
569
   * if there *is* an error, we'll never call the callback passed to
570
   * jabber_caps_get_info. We will still free all of our data, though.
571
   */
572
0
  --userdata->data->extOutstanding;
573
574
0
  for (child = xmlnode_get_child(query, "feature"); child;
575
0
          child = xmlnode_get_next_twin(child)) {
576
0
    const char *var = xmlnode_get_attrib(child, "var");
577
0
    if (var)
578
0
      features = g_list_prepend(features, g_strdup(var));
579
0
  }
580
581
0
  g_hash_table_insert(node_exts->exts, g_strdup(userdata->name), features);
582
0
  schedule_caps_save();
583
584
  /* Are we done? */
585
0
  if (userdata->data->info && userdata->data->extOutstanding == 0)
586
0
    jabber_caps_get_info_complete(userdata->data);
587
588
0
  cbplususerdata_unref(userdata->data);
589
0
  g_free(userdata);
590
0
}
591
592
void jabber_caps_get_info(JabberStream *js, const char *who, const char *node,
593
        const char *ver, const char *hash, char **exts,
594
        jabber_caps_get_info_cb cb, gpointer user_data)
595
0
{
596
0
  JabberCapsClientInfo *info;
597
0
  JabberCapsTuple key;
598
0
  jabber_caps_cbplususerdata *userdata;
599
600
0
  if (exts && hash) {
601
0
    purple_debug_misc("jabber", "Ignoring exts in new-style caps from %s\n",
602
0
                      who);
603
0
    g_strfreev(exts);
604
0
    exts = NULL;
605
0
  }
606
607
  /* Using this in a read-only fashion, so the cast is OK */
608
0
  key.node = (char *)node;
609
0
  key.ver = (char *)ver;
610
0
  key.hash = (char *)hash;
611
612
0
  info = g_hash_table_lookup(capstable, &key);
613
0
  if (info && hash) {
614
    /* v1.5 - We already have all the information we care about */
615
0
    if (cb)
616
0
      cb(info, NULL, user_data);
617
0
    return;
618
0
  }
619
620
0
  userdata = g_new0(jabber_caps_cbplususerdata, 1);
621
  /* We start out with 0 references. Every query takes one */
622
0
  userdata->cb = cb;
623
0
  userdata->cb_data = user_data;
624
0
  userdata->who = g_strdup(who);
625
0
  userdata->node = g_strdup(node);
626
0
  userdata->ver = g_strdup(ver);
627
0
  userdata->hash = g_strdup(hash);
628
629
0
  if (info) {
630
0
    userdata->info = info;
631
0
  } else {
632
    /* If we don't have the basic information about the client, we need
633
     * to fetch it. */
634
0
    JabberIq *iq;
635
0
    xmlnode *query;
636
0
    char *nodever;
637
638
0
    iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
639
0
    query = xmlnode_get_child_with_namespace(iq->node, "query",
640
0
          NS_DISCO_INFO);
641
0
    nodever = g_strdup_printf("%s#%s", node, ver);
642
0
    xmlnode_set_attrib(query, "node", nodever);
643
0
    g_free(nodever);
644
0
    xmlnode_set_attrib(iq->node, "to", who);
645
646
0
    cbplususerdata_ref(userdata);
647
648
0
    jabber_iq_set_callback(iq, jabber_caps_client_iqcb, userdata);
649
0
    jabber_iq_send(iq);
650
0
  }
651
652
  /* Are there any exts that we don't recognize? */
653
0
  if (exts) {
654
0
    JabberCapsNodeExts *node_exts;
655
0
    int i;
656
657
0
    if (info) {
658
0
      if (info->exts)
659
0
        node_exts = info->exts;
660
0
      else
661
0
        node_exts = info->exts = jabber_caps_find_exts_by_node(node);
662
0
    } else
663
      /* We'll put it in later once we have the client info */
664
0
      node_exts = userdata->node_exts = jabber_caps_find_exts_by_node(node);
665
666
0
    for (i = 0; exts[i]; ++i) {
667
0
      userdata->exts = g_list_prepend(userdata->exts, exts[i]);
668
      /* Look it up if we don't already know what it means */
669
0
      if (!g_hash_table_lookup(node_exts->exts, exts[i])) {
670
0
        JabberIq *iq;
671
0
        xmlnode *query;
672
0
        char *nodeext;
673
0
        ext_iq_data *cbdata = g_new(ext_iq_data, 1);
674
675
0
        cbdata->name = exts[i];
676
0
        cbdata->data = cbplususerdata_ref(userdata);
677
678
0
        iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO);
679
0
        query = xmlnode_get_child_with_namespace(iq->node, "query",
680
0
                    NS_DISCO_INFO);
681
0
        nodeext = g_strdup_printf("%s#%s", node, exts[i]);
682
0
        xmlnode_set_attrib(query, "node", nodeext);
683
0
        g_free(nodeext);
684
0
        xmlnode_set_attrib(iq->node, "to", who);
685
686
0
        jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, cbdata);
687
0
        jabber_iq_send(iq);
688
689
0
        ++userdata->extOutstanding;
690
0
      }
691
0
      exts[i] = NULL;
692
0
    }
693
    /* All the strings are now part of the GList, so don't need
694
     * g_strfreev. */
695
0
    g_free(exts);
696
0
  }
697
698
0
  if (userdata->info && userdata->extOutstanding == 0) {
699
    /* Start with 1 ref so the below functions are happy */
700
0
    userdata->ref = 1;
701
702
    /* We have everything we need right now */
703
0
    jabber_caps_get_info_complete(userdata);
704
0
    cbplususerdata_unref(userdata);
705
0
  }
706
0
}
707
708
static gint
709
jabber_xdata_compare(gconstpointer a, gconstpointer b)
710
0
{
711
0
  const xmlnode *aformtypefield = a;
712
0
  const xmlnode *bformtypefield = b;
713
0
  char *aformtype;
714
0
  char *bformtype;
715
0
  int result;
716
717
0
  aformtype = jabber_x_data_get_formtype(aformtypefield);
718
0
  bformtype = jabber_x_data_get_formtype(bformtypefield);
719
720
0
  result = strcmp(aformtype, bformtype);
721
0
  g_free(aformtype);
722
0
  g_free(bformtype);
723
0
  return result;
724
0
}
725
726
JabberCapsClientInfo *jabber_caps_parse_client_info(xmlnode *query)
727
0
{
728
0
  xmlnode *child;
729
0
  JabberCapsClientInfo *info;
730
731
0
  if (!query || !purple_strequal(query->name, "query") ||
732
0
      !purple_strequal(query->xmlns, NS_DISCO_INFO))
733
0
    return NULL;
734
735
0
  info = g_new0(JabberCapsClientInfo, 1);
736
737
0
  for(child = query->child; child; child = child->next) {
738
0
    if (child->type != XMLNODE_TYPE_TAG)
739
0
      continue;
740
0
    if (purple_strequal(child->name, "identity")) {
741
      /* parse identity */
742
0
      const char *category = xmlnode_get_attrib(child, "category");
743
0
      const char *type = xmlnode_get_attrib(child, "type");
744
0
      const char *name = xmlnode_get_attrib(child, "name");
745
0
      const char *lang = xmlnode_get_attrib(child, "lang");
746
0
      JabberIdentity *id;
747
748
0
      if (!category || !type)
749
0
        continue;
750
751
0
      id = g_new0(JabberIdentity, 1);
752
0
      id->category = g_strdup(category);
753
0
      id->type = g_strdup(type);
754
0
      id->name = g_strdup(name);
755
0
      id->lang = g_strdup(lang);
756
757
0
      info->identities = g_list_append(info->identities, id);
758
0
    } else if (purple_strequal(child->name, "feature")) {
759
      /* parse feature */
760
0
      const char *var = xmlnode_get_attrib(child, "var");
761
0
      if (var)
762
0
        info->features = g_list_prepend(info->features, g_strdup(var));
763
0
    } else if (purple_strequal(child->name, "x")) {
764
0
      if (purple_strequal(child->xmlns, "jabber:x:data")) {
765
        /* x-data form */
766
0
        xmlnode *dataform = xmlnode_copy(child);
767
0
        info->forms = g_list_append(info->forms, dataform);
768
0
      }
769
0
    }
770
0
  }
771
0
  return info;
772
0
}
773
774
static gint jabber_caps_xdata_field_compare(gconstpointer a, gconstpointer b)
775
0
{
776
0
  const JabberDataFormField *ac = a;
777
0
  const JabberDataFormField *bc = b;
778
779
0
  return strcmp(ac->var, bc->var);
780
0
}
781
782
static GList* jabber_caps_xdata_get_fields(const xmlnode *x)
783
0
{
784
0
  GList *fields = NULL;
785
0
  xmlnode *field;
786
787
0
  if (!x)
788
0
    return NULL;
789
790
0
  for (field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) {
791
0
    xmlnode *value;
792
0
    JabberDataFormField *xdatafield = g_new0(JabberDataFormField, 1);
793
0
    xdatafield->var = g_strdup(xmlnode_get_attrib(field, "var"));
794
795
0
    for (value = xmlnode_get_child(field, "value"); value; value = xmlnode_get_next_twin(value)) {
796
0
      gchar *val = xmlnode_get_data(value);
797
0
      xdatafield->values = g_list_append(xdatafield->values, val);
798
0
    }
799
800
0
    xdatafield->values = g_list_sort(xdatafield->values, (GCompareFunc)strcmp);
801
0
    fields = g_list_append(fields, xdatafield);
802
0
  }
803
804
0
  fields = g_list_sort(fields, jabber_caps_xdata_field_compare);
805
0
  return fields;
806
0
}
807
808
static void
809
append_escaped_string(PurpleCipherContext *context, const gchar *str)
810
0
{
811
0
  if (str && *str) {
812
0
    char *tmp = g_markup_escape_text(str, -1);
813
0
    purple_cipher_context_append(context, (const guchar *)tmp, strlen(tmp));
814
0
    g_free(tmp);
815
0
  }
816
817
0
  purple_cipher_context_append(context, (const guchar *)"<", 1);
818
0
}
819
820
gchar *jabber_caps_calculate_hash(JabberCapsClientInfo *info, const char *hash)
821
0
{
822
0
  GList *node;
823
0
  PurpleCipherContext *context;
824
0
  guint8 checksum[20];
825
0
  gsize checksum_size = 20;
826
0
  gboolean success;
827
828
0
  if (!info || !(context = purple_cipher_context_new_by_name(hash, NULL)))
829
0
    return NULL;
830
831
  /* sort identities, features and x-data forms */
832
0
  info->identities = g_list_sort(info->identities, jabber_identity_compare);
833
0
  info->features = g_list_sort(info->features, (GCompareFunc)strcmp);
834
0
  info->forms = g_list_sort(info->forms, jabber_xdata_compare);
835
836
  /* Add identities to the hash data */
837
0
  for (node = info->identities; node; node = node->next) {
838
0
    JabberIdentity *id = (JabberIdentity*)node->data;
839
0
    char *category = g_markup_escape_text(id->category, -1);
840
0
    char *type = g_markup_escape_text(id->type, -1);
841
0
    char *lang = NULL;
842
0
    char *name = NULL;
843
0
    char *tmp;
844
845
0
    if (id->lang)
846
0
      lang = g_markup_escape_text(id->lang, -1);
847
0
    if (id->name)
848
0
      name = g_markup_escape_text(id->name, -1);
849
850
0
    tmp = g_strconcat(category, "/", type, "/", lang ? lang : "",
851
0
                      "/", name ? name : "", "<", NULL);
852
853
0
    purple_cipher_context_append(context, (const guchar *)tmp, strlen(tmp));
854
855
0
    g_free(tmp);
856
0
    g_free(category);
857
0
    g_free(type);
858
0
    g_free(lang);
859
0
    g_free(name);
860
0
  }
861
862
  /* concat features to the verification string */
863
0
  for (node = info->features; node; node = node->next) {
864
0
    append_escaped_string(context, node->data);
865
0
  }
866
867
  /* concat x-data forms to the verification string */
868
0
  for(node = info->forms; node; node = node->next) {
869
0
    xmlnode *data = (xmlnode *)node->data;
870
0
    gchar *formtype = jabber_x_data_get_formtype(data);
871
0
    GList *fields = jabber_caps_xdata_get_fields(data);
872
873
    /* append FORM_TYPE's field value to the verification string */
874
0
    append_escaped_string(context, formtype);
875
0
    g_free(formtype);
876
877
0
    while (fields) {
878
0
      GList *value;
879
0
      JabberDataFormField *field = (JabberDataFormField*)fields->data;
880
881
0
      if (!purple_strequal(field->var, "FORM_TYPE")) {
882
        /* Append the "var" attribute */
883
0
        append_escaped_string(context, field->var);
884
        /* Append <value/> elements' cdata */
885
0
        for (value = field->values; value; value = value->next) {
886
0
          append_escaped_string(context, value->data);
887
0
          g_free(value->data);
888
0
        }
889
0
      }
890
891
0
      g_free(field->var);
892
0
      g_list_free(field->values);
893
894
0
      fields = g_list_delete_link(fields, fields);
895
0
    }
896
0
  }
897
898
  /* generate hash */
899
0
  success = purple_cipher_context_digest(context, checksum_size,
900
0
                                         checksum, &checksum_size);
901
902
0
  purple_cipher_context_destroy(context);
903
904
0
  return (success ? purple_base64_encode(checksum, checksum_size) : NULL);
905
0
}
906
907
0
void jabber_caps_calculate_own_hash(JabberStream *js) {
908
0
  JabberCapsClientInfo info;
909
0
  GList *iter = 0;
910
0
  GList *features = 0;
911
912
0
  if (!jabber_identities && !jabber_features) {
913
    /* This really shouldn't ever happen */
914
0
    purple_debug_warning("jabber", "No features or identities, cannot calculate own caps hash.\n");
915
0
    g_free(js->caps_hash);
916
0
    js->caps_hash = NULL;
917
0
    return;
918
0
  }
919
920
  /* build the currently-supported list of features */
921
0
  if (jabber_features) {
922
0
    for (iter = jabber_features; iter; iter = iter->next) {
923
0
      JabberFeature *feat = iter->data;
924
0
      if(!feat->is_enabled || feat->is_enabled(js, feat->namespace)) {
925
0
        features = g_list_append(features, feat->namespace);
926
0
      }
927
0
    }
928
0
  }
929
930
0
  info.features = features;
931
  /* TODO: This copy can go away, I think, since jabber_identities
932
   * is pre-sorted, so the sort in calculate_hash should be idempotent.
933
   * However, I want to test that. --darkrain
934
   */
935
0
  info.identities = g_list_copy(jabber_identities);
936
0
  info.forms = NULL;
937
938
0
  g_free(js->caps_hash);
939
0
  js->caps_hash = jabber_caps_calculate_hash(&info, "sha1");
940
0
  g_list_free(info.identities);
941
0
  g_list_free(info.features);
942
0
}
943
944
const gchar* jabber_caps_get_own_hash(JabberStream *js)
945
0
{
946
0
  if (!js->caps_hash)
947
0
    jabber_caps_calculate_own_hash(js);
948
949
0
  return js->caps_hash;
950
0
}
951
952
void jabber_caps_broadcast_change()
953
0
{
954
0
  GList *node, *accounts = purple_accounts_get_all_active();
955
956
0
  for (node = accounts; node; node = node->next) {
957
0
    PurpleAccount *account = node->data;
958
0
    const char *prpl_id = purple_account_get_protocol_id(account);
959
0
    if (purple_strequal("prpl-jabber", prpl_id) && purple_account_is_connected(account)) {
960
0
      PurpleConnection *gc = purple_account_get_connection(account);
961
0
      jabber_presence_send(gc->proto_data, TRUE);
962
0
    }
963
0
  }
964
965
0
  g_list_free(accounts);
966
0
}
967