1 /*
2 * caps.c -- Entity Capabilities Cache for mcabber
3 *
4 * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <glib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25
26 #include "settings.h"
27 #include "utils.h"
28
29 typedef struct {
30 char *category;
31 char *type;
32 char *name;
33 } identity_t;
34
35 typedef struct {
36 GHashTable *fields;
37 } dataform_t;
38
39 typedef struct {
40 GHashTable *identities;
41 GHashTable *features;
42 GHashTable *forms;
43 } caps_t;
44
45 static GHashTable *caps_cache = NULL;
46
caps_destroy(gpointer data)47 void caps_destroy(gpointer data)
48 {
49 caps_t *c = data;
50 g_hash_table_destroy(c->identities);
51 g_hash_table_destroy(c->features);
52 g_hash_table_destroy(c->forms);
53 g_free(c);
54 }
55
identity_destroy(gpointer data)56 void identity_destroy(gpointer data)
57 {
58 identity_t *i = data;
59 g_free(i->category);
60 g_free(i->type);
61 g_free(i->name);
62 g_free(i);
63 }
64
form_destroy(gpointer data)65 void form_destroy(gpointer data)
66 {
67 dataform_t *f = data;
68 g_hash_table_destroy(f->fields);
69 g_free(f);
70 }
71
field_destroy(gpointer data)72 void field_destroy(gpointer data)
73 {
74 GList *v = data;
75 g_list_foreach (v, (GFunc) g_free, NULL);
76 g_list_free (v);
77 }
78
caps_init(void)79 void caps_init(void)
80 {
81 if (!caps_cache)
82 caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
83 g_free, caps_destroy);
84 }
85
caps_free(void)86 void caps_free(void)
87 {
88 if (caps_cache) {
89 g_hash_table_destroy(caps_cache);
90 caps_cache = NULL;
91 }
92 }
93
caps_add(const char * hash)94 void caps_add(const char *hash)
95 {
96 if (!hash)
97 return;
98 caps_t *c = g_new0(caps_t, 1);
99 c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
100 c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
101 c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
102 g_hash_table_replace(caps_cache, g_strdup(hash), c);
103 }
104
caps_remove(const char * hash)105 void caps_remove(const char *hash)
106 {
107 if (!hash)
108 return;
109 g_hash_table_remove(caps_cache, hash);
110 }
111
112 /* if hash is not verified, this will bind capabilities set only with bare jid */
caps_move_to_local(char * hash,char * bjid)113 void caps_move_to_local(char *hash, char *bjid)
114 {
115 char *orig_hash;
116 caps_t *c = NULL;
117 if (!hash || !bjid)
118 return;
119 g_hash_table_lookup_extended(caps_cache, hash, (gpointer*)&orig_hash, (gpointer*)&c);
120 if (c) {
121 g_hash_table_steal(caps_cache, hash);
122 g_free(orig_hash);
123 g_hash_table_replace(caps_cache, g_strdup_printf("%s/#%s", bjid, hash), c);
124 // solidus is guaranteed to never appear in bare jid
125 // hash will not appear in base64 encoded hash
126 // sequence "/#" is deterministic separator, and allows to identify local cache entry
127 }
128 }
129
130 /*if bjid is NULL, it will check only verified hashes */
caps_has_hash(const char * hash,const char * bjid)131 int caps_has_hash(const char *hash, const char *bjid)
132 {
133 caps_t *c = NULL;
134 if (!hash)
135 return 0;
136 c = g_hash_table_lookup(caps_cache, hash);
137 if (!c && bjid) {
138 char *key = g_strdup_printf("%s/#%s", bjid, hash);
139 c = g_hash_table_lookup(caps_cache, key);
140 g_free(key);
141 }
142 return (c != NULL);
143 }
144
caps_add_identity(const char * hash,const char * category,const char * name,const char * type,const char * lang)145 void caps_add_identity(const char *hash,
146 const char *category,
147 const char *name,
148 const char *type,
149 const char *lang)
150 {
151 caps_t *c;
152 if (!hash || !category || !type)
153 return;
154 if (!lang)
155 lang = "";
156
157 c = g_hash_table_lookup(caps_cache, hash);
158 if (c) {
159 identity_t *i = g_new0(identity_t, 1);
160
161 i->category = g_strdup(category);
162 i->name = g_strdup(name);
163 i->type = g_strdup(type);
164 g_hash_table_replace(c->identities, g_strdup(lang), i);
165 }
166 }
167
caps_set_identity(char * hash,const char * category,const char * name,const char * type)168 void caps_set_identity(char *hash,
169 const char *category,
170 const char *name,
171 const char *type)
172 {
173 caps_add_identity(hash, category, name, type, NULL);
174 }
175
caps_add_dataform(const char * hash,const char * formtype)176 void caps_add_dataform(const char *hash, const char *formtype)
177 {
178 caps_t *c;
179 if (!formtype)
180 return;
181 c = g_hash_table_lookup(caps_cache, hash);
182 if (c) {
183 dataform_t *d = g_new0(dataform_t, 1);
184 char *f = g_strdup(formtype);
185
186 d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
187 g_hash_table_replace(c->forms, f, d);
188 }
189 }
190
_strcmp_sort(gconstpointer a,gconstpointer b)191 gint _strcmp_sort(gconstpointer a, gconstpointer b)
192 {
193 return g_strcmp0(a, b);
194 }
195
caps_add_dataform_field(const char * hash,const char * formtype,const char * field,const char * value)196 void caps_add_dataform_field(const char *hash, const char *formtype,
197 const char *field, const char *value)
198 {
199 caps_t *c;
200 if (!formtype || !field || !value)
201 return;
202 c = g_hash_table_lookup(caps_cache, hash);
203 if (c) {
204 dataform_t *d;
205 d = g_hash_table_lookup(c->forms, formtype);
206 if (d) {
207 gpointer key, val;
208 char *f;
209 GList *v = NULL;
210 if (g_hash_table_lookup_extended(d->fields, field, &key, &val)) {
211 g_hash_table_steal(d->fields, field);
212 g_free(key);
213 v = val;
214 }
215 f = g_strdup(field);
216 v = g_list_insert_sorted(v, g_strdup(value), _strcmp_sort);
217 g_hash_table_replace(d->fields, f, v);
218 }
219 }
220 }
221
caps_add_feature(const char * hash,const char * feature)222 void caps_add_feature(const char *hash, const char *feature)
223 {
224 caps_t *c;
225 if (!hash || !feature)
226 return;
227 c = g_hash_table_lookup(caps_cache, hash);
228 if (c) {
229 char *f = g_strdup(feature);
230 g_hash_table_replace(c->features, f, f);
231 }
232 }
233
234 /* If hash is verified, then bare jid is ignored.
235 * If there is no globally verified hash, and bare jid is not null,
236 * then local storage for that jid will be checked */
caps_has_feature(const char * hash,char * feature,char * bjid)237 int caps_has_feature(const char *hash, char *feature, char *bjid)
238 {
239 caps_t *c = NULL;
240 if (!hash || !feature)
241 return 0;
242 c = g_hash_table_lookup(caps_cache, hash);
243 if (!c && bjid) {
244 char *key = g_strdup_printf("%s/#%s", bjid, hash);
245 c = g_hash_table_lookup(caps_cache, key);
246 g_free(key);
247 }
248 if (c)
249 return (g_hash_table_lookup(c->features, feature) != NULL);
250 return 0;
251 }
252
253 static GFunc _foreach_function;
254
_caps_foreach_helper(gpointer key,gpointer value,gpointer user_data)255 void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data)
256 {
257 // GFunc func = (GFunc)user_data;
258 _foreach_function(value, user_data);
259 }
260
caps_foreach_feature(const char * hash,GFunc func,gpointer user_data)261 void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data)
262 {
263 caps_t *c;
264 if (!hash)
265 return;
266 c = g_hash_table_lookup(caps_cache, hash);
267 if (!c)
268 return;
269 _foreach_function = func;
270 g_hash_table_foreach(c->features, _caps_foreach_helper, user_data);
271 }
272
273 // Generates the sha1 hash for the special capability "" and returns it
caps_generate(void)274 const char *caps_generate(void)
275 {
276 GList *features, *langs;
277 GChecksum *sha1;
278 guint8 digest[20];
279 gsize digest_size = 20;
280 gchar *hash, *old_hash = NULL;
281 caps_t *old_caps, *c;
282 gpointer key;
283
284 if (!g_hash_table_lookup_extended(caps_cache, "", &key, (gpointer *)&c))
285 return NULL;
286
287 g_hash_table_steal(caps_cache, "");
288 g_free(key);
289
290 sha1 = g_checksum_new(G_CHECKSUM_SHA1);
291
292 langs = g_hash_table_get_keys(c->identities);
293 langs = g_list_sort(langs, _strcmp_sort);
294 {
295 identity_t *i;
296 GList *lang;
297 char *identity_S;
298 for (lang=langs; lang; lang=lang->next) {
299 i = g_hash_table_lookup(c->identities, lang->data);
300 identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
301 (char *)lang->data, i->name ? i->name : "");
302 g_checksum_update(sha1, (guchar *)identity_S, -1);
303 g_free(identity_S);
304 }
305 }
306 g_list_free(langs);
307
308 features = g_hash_table_get_values(c->features);
309 features = g_list_sort(features, _strcmp_sort);
310 {
311 GList *feature;
312 for (feature=features; feature; feature=feature->next) {
313 g_checksum_update(sha1, feature->data, -1);
314 g_checksum_update(sha1, (guchar *)"<", -1);
315 }
316 }
317 g_list_free(features);
318
319 g_checksum_get_digest(sha1, digest, &digest_size);
320 hash = g_base64_encode(digest, digest_size);
321 g_checksum_free(sha1);
322 g_hash_table_lookup_extended(caps_cache, hash,
323 (gpointer *)&old_hash, (gpointer *)&old_caps);
324 g_hash_table_insert(caps_cache, hash, c);
325 if (old_hash)
326 return old_hash;
327 else
328 return hash;
329 }
330
caps_verify(const char * hash,char * function)331 gboolean caps_verify(const char *hash, char *function)
332 {
333 GList *features, *langs, *forms;
334 GChecksum *checksum;
335 guint8 digest[20];
336 gsize digest_size = 20;
337 gchar *local_hash;
338 gboolean match = FALSE;
339 caps_t *c = g_hash_table_lookup(caps_cache, hash);
340
341 if (!g_strcmp0(function, "sha-1")) {
342 checksum = g_checksum_new(G_CHECKSUM_SHA1);
343 } else if (!g_strcmp0(function, "md5")) {
344 checksum = g_checksum_new(G_CHECKSUM_MD5);
345 digest_size = 16;
346 } else
347 return FALSE;
348
349 langs = g_hash_table_get_keys(c->identities);
350 langs = g_list_sort(langs, _strcmp_sort);
351 {
352 identity_t *i;
353 GList *lang;
354 char *identity_S;
355 for (lang=langs; lang; lang=lang->next) {
356 i = g_hash_table_lookup(c->identities, lang->data);
357 identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
358 (char *)lang->data, i->name ? i->name : "");
359 g_checksum_update(checksum, (guchar *)identity_S, -1);
360 g_free(identity_S);
361 }
362 }
363 g_list_free(langs);
364
365 features = g_hash_table_get_values(c->features);
366 features = g_list_sort(features, _strcmp_sort);
367 {
368 GList *feature;
369 for (feature=features; feature; feature=feature->next) {
370 g_checksum_update(checksum, feature->data, -1);
371 g_checksum_update(checksum, (guchar *)"<", -1);
372 }
373 }
374 g_list_free(features);
375
376 forms = g_hash_table_get_keys(c->forms);
377 forms = g_list_sort(forms, _strcmp_sort);
378 {
379 dataform_t *d;
380 GList *form, *fields;
381 for (form=forms; form; form=form->next) {
382 d = g_hash_table_lookup(c->forms, form->data);
383 g_checksum_update(checksum, form->data, -1);
384 g_checksum_update(checksum, (guchar *)"<", -1);
385 fields = g_hash_table_get_keys(d->fields);
386 fields = g_list_sort(fields, _strcmp_sort);
387 {
388 GList *field;
389 GList *values;
390 for (field=fields; field; field=field->next) {
391 g_checksum_update(checksum, field->data, -1);
392 g_checksum_update(checksum, (guchar *)"<", -1);
393 values = g_hash_table_lookup(d->fields, field->data);
394 {
395 GList *value;
396 for (value=values; value; value=value->next) {
397 g_checksum_update(checksum, value->data, -1);
398 g_checksum_update(checksum, (guchar *)"<", -1);
399 }
400 }
401 }
402 }
403 g_list_free(fields);
404 }
405 }
406 g_list_free(forms);
407
408 g_checksum_get_digest(checksum, digest, &digest_size);
409 local_hash = g_base64_encode(digest, digest_size);
410 g_checksum_free(checksum);
411
412 match = !g_strcmp0(hash, local_hash);
413
414 g_free(local_hash);
415 return match;
416 }
417
caps_get_filename(const char * hash)418 static gchar* caps_get_filename(const char* hash)
419 {
420 gchar *hash_fs;
421 gchar *dir = (gchar *) settings_opt_get ("caps_directory");
422 gchar *file = NULL;
423
424 if (!dir)
425 goto caps_filename_return;
426
427 hash_fs = g_strdup (hash);
428 {
429 const gchar *valid_fs =
430 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=";
431 g_strcanon(hash_fs, valid_fs, '-');
432 }
433
434 dir = expand_filename (dir);
435 file = g_strdup_printf ("%s/%s.ini", dir, hash_fs);
436 g_free(dir);
437 g_free(hash_fs);
438
439 caps_filename_return:
440 return file;
441 }
442
443 /* Store capabilities set in GKeyFile. To be used with verified hashes only */
caps_copy_to_persistent(const char * hash,char * xml)444 void caps_copy_to_persistent(const char* hash, char* xml)
445 {
446 gchar *file;
447 GList *features, *langs, *forms;
448 GKeyFile *key_file;
449 caps_t *c;
450 int fd;
451
452 g_free (xml);
453
454 c = g_hash_table_lookup (caps_cache, hash);
455 if (!c)
456 goto caps_copy_return;
457
458 file = caps_get_filename (hash);
459 if (!file)
460 goto caps_copy_return;
461
462 fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
463 if (fd == -1)
464 goto caps_copy_exists;
465
466 key_file = g_key_file_new ();
467 g_key_file_set_comment (key_file, NULL, NULL,
468 "This is autogenerated file. Please do not modify.",
469 NULL);
470
471 langs = g_hash_table_get_keys (c->identities);
472 {
473 identity_t *i;
474 GList *lang;
475 gchar *group;
476 for (lang=langs; lang; lang=lang->next) {
477 i = g_hash_table_lookup (c->identities, lang->data);
478 group = g_strdup_printf("identity_%s", (gchar *)lang->data);
479 g_key_file_set_string (key_file, group, "category", i->category);
480 g_key_file_set_string (key_file, group, "type", i->type);
481 g_key_file_set_string (key_file, group, "name", i->name);
482 g_free (group);
483 }
484 }
485 g_list_free (langs);
486
487 features = g_hash_table_get_values (c->features);
488 {
489 GList *feature;
490 gchar **string_list;
491 gint i;
492
493 i = g_list_length (features);
494 string_list = g_new (gchar*, i + 1);
495 i = 0;
496 for (feature=features; feature; feature=feature->next) {
497 string_list[i] = g_strdup(feature->data);
498 ++i;
499 }
500 string_list[i] = NULL;
501
502 g_key_file_set_string_list (key_file, "features", "features",
503 (const gchar**)string_list, i);
504 g_strfreev (string_list);
505 }
506 g_list_free (features);
507
508 forms = g_hash_table_get_keys(c->forms);
509 {
510 dataform_t *d;
511 GList *form, *fields;
512 gchar *group;
513 for (form=forms; form; form=form->next) {
514 d = g_hash_table_lookup (c->forms, form->data);
515 group = g_strdup_printf ("form_%s", (gchar *)form->data);
516 fields = g_hash_table_get_keys(d->fields);
517 {
518 GList *field;
519 GList *values;
520 for (field=fields; field; field=field->next) {
521 values = g_hash_table_lookup (d->fields, field->data);
522 {
523 GList *value;
524 gchar **string_list;
525 gint i;
526 i = g_list_length (values);
527 string_list = g_new (gchar*, i + 1);
528 i = 0;
529 for (value=values; value; value=value->next) {
530 string_list[i] = g_strdup(value->data);
531 ++i;
532 }
533 string_list[i] = NULL;
534
535 g_key_file_set_string_list (key_file, group, field->data,
536 (const gchar**)string_list, i);
537
538 g_strfreev (string_list);
539 }
540 }
541 }
542 g_list_free(fields);
543 g_free (group);
544 }
545 }
546 g_list_free (forms);
547
548 {
549 gchar *data;
550 gsize length;
551 data = g_key_file_to_data (key_file, &length, NULL);
552 write (fd, data, length);
553 g_free(data);
554 close (fd);
555 }
556
557 g_key_file_free(key_file);
558 caps_copy_exists:
559 g_free(file);
560 caps_copy_return:
561 return;
562 }
563
564 /* Restore capabilities from GKeyFile. Hash is not verified afterwards */
caps_restore_from_persistent(const char * hash)565 gboolean caps_restore_from_persistent (const char* hash)
566 {
567 gchar *file;
568 GKeyFile *key_file;
569 gchar **groups, **group;
570 gboolean restored = FALSE;
571
572 file = caps_get_filename (hash);
573 if (!file)
574 goto caps_restore_no_file;
575
576 key_file = g_key_file_new ();
577 if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, NULL))
578 goto caps_restore_bad_file;
579
580 caps_add(hash);
581
582 groups = g_key_file_get_groups (key_file, NULL);
583 for (group = groups; *group; ++group) {
584 if (!g_strcmp0(*group, "features")) {
585 gchar **features, **feature;
586 features = g_key_file_get_string_list (key_file, *group, "features",
587 NULL, NULL);
588 for (feature = features; *feature; ++feature) {
589 caps_add_feature(hash, *feature);
590 }
591
592 g_strfreev (features);
593 } else if (g_str_has_prefix (*group, "identity_")) {
594 gchar *category, *type, *name, *lang;
595
596 category = g_key_file_get_string(key_file, *group, "category", NULL);
597 type = g_key_file_get_string(key_file, *group, "type", NULL);
598 name = g_key_file_get_string(key_file, *group, "name", NULL);
599 lang = *group + 9; /* "identity_" */
600
601 caps_add_identity(hash, category, name, type, lang);
602 g_free(category);
603 g_free(type);
604 g_free(name);
605 } else if (g_str_has_prefix (*group, "form_")) {
606 gchar *formtype;
607 gchar **fields, **field;
608 formtype = *group + 5; /* "form_" */
609 caps_add_dataform (hash, formtype);
610
611 fields = g_key_file_get_keys(key_file, *group, NULL, NULL);
612 for (field = fields; *field; ++field) {
613 gchar **values, **value;
614 values = g_key_file_get_string_list (key_file, *group, *field,
615 NULL, NULL);
616 for (value = values; *value; ++value) {
617 caps_add_dataform_field (hash, formtype, *field, *value);
618 }
619 g_strfreev (values);
620 }
621 g_strfreev (fields);
622 }
623 }
624 g_strfreev(groups);
625 restored = TRUE;
626
627 caps_restore_bad_file:
628 g_key_file_free (key_file);
629 g_free (file);
630 caps_restore_no_file:
631 return restored;
632 }
633
634 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
635