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