Personal copy of mcabber
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

634 lines
17 KiB

/*
* caps.c -- Entity Capabilities Cache for mcabber
*
* Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "settings.h"
#include "utils.h"
typedef struct {
char *category;
char *type;
char *name;
} identity_t;
typedef struct {
GHashTable *fields;
} dataform_t;
typedef struct {
GHashTable *identities;
GHashTable *features;
GHashTable *forms;
} caps_t;
static GHashTable *caps_cache = NULL;
void caps_destroy(gpointer data)
{
caps_t *c = data;
g_hash_table_destroy(c->identities);
g_hash_table_destroy(c->features);
g_hash_table_destroy(c->forms);
g_free(c);
}
void identity_destroy(gpointer data)
{
identity_t *i = data;
g_free(i->category);
g_free(i->type);
g_free(i->name);
g_free(i);
}
void form_destroy(gpointer data)
{
dataform_t *f = data;
g_hash_table_destroy(f->fields);
g_free(f);
}
void field_destroy(gpointer data)
{
GList *v = data;
g_list_foreach (v, (GFunc) g_free, NULL);
g_list_free (v);
}
void caps_init(void)
{
if (!caps_cache)
caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, caps_destroy);
}
void caps_free(void)
{
if (caps_cache) {
g_hash_table_destroy(caps_cache);
caps_cache = NULL;
}
}
void caps_add(const char *hash)
{
if (!hash)
return;
caps_t *c = g_new0(caps_t, 1);
c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
c->identities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, identity_destroy);
c->forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, form_destroy);
g_hash_table_replace(caps_cache, g_strdup(hash), c);
}
void caps_remove(const char *hash)
{
if (!hash)
return;
g_hash_table_remove(caps_cache, hash);
}
/* if hash is not verified, this will bind capabilities set only with bare jid */
void caps_move_to_local(char *hash, char *bjid)
{
char *orig_hash;
caps_t *c = NULL;
if (!hash || !bjid)
return;
g_hash_table_lookup_extended(caps_cache, hash, (gpointer*)&orig_hash, (gpointer*)&c);
if (c) {
g_hash_table_steal(caps_cache, hash);
g_free(orig_hash);
g_hash_table_replace(caps_cache, g_strdup_printf("%s/#%s", bjid, hash), c);
// solidus is guaranteed to never appear in bare jid
// hash will not appear in base64 encoded hash
// sequence "/#" is deterministic separator, and allows to identify local cache entry
}
}
/*if bjid is NULL, it will check only verified hashes */
int caps_has_hash(const char *hash, const char *bjid)
{
caps_t *c = NULL;
if (!hash)
return 0;
c = g_hash_table_lookup(caps_cache, hash);
if (!c && bjid) {
char *key = g_strdup_printf("%s/#%s", bjid, hash);
c = g_hash_table_lookup(caps_cache, key);
g_free(key);
}
return (c != NULL);
}
void caps_add_identity(const char *hash,
const char *category,
const char *name,
const char *type,
const char *lang)
{
caps_t *c;
if (!hash || !category || !type)
return;
if (!lang)
lang = "";
c = g_hash_table_lookup(caps_cache, hash);
if (c) {
identity_t *i = g_new0(identity_t, 1);
i->category = g_strdup(category);
i->name = g_strdup(name);
i->type = g_strdup(type);
g_hash_table_replace(c->identities, g_strdup(lang), i);
}
}
void caps_set_identity(char *hash,
const char *category,
const char *name,
const char *type)
{
caps_add_identity(hash, category, name, type, NULL);
}
void caps_add_dataform(const char *hash, const char *formtype)
{
caps_t *c;
if (!formtype)
return;
c = g_hash_table_lookup(caps_cache, hash);
if (c) {
dataform_t *d = g_new0(dataform_t, 1);
char *f = g_strdup(formtype);
d->fields = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, field_destroy);
g_hash_table_replace(c->forms, f, d);
}
}
gint _strcmp_sort(gconstpointer a, gconstpointer b)
{
return g_strcmp0(a, b);
}
void caps_add_dataform_field(const char *hash, const char *formtype,
const char *field, const char *value)
{
caps_t *c;
if (!formtype || !field || !value)
return;
c = g_hash_table_lookup(caps_cache, hash);
if (c) {
dataform_t *d;
d = g_hash_table_lookup(c->forms, formtype);
if (d) {
gpointer key, val;
char *f;
GList *v = NULL;
if (g_hash_table_lookup_extended(d->fields, field, &key, &val)) {
g_hash_table_steal(d->fields, field);
g_free(key);
v = val;
}
f = g_strdup(field);
v = g_list_insert_sorted(v, g_strdup(value), _strcmp_sort);
g_hash_table_replace(d->fields, f, v);
}
}
}
void caps_add_feature(const char *hash, const char *feature)
{
caps_t *c;
if (!hash || !feature)
return;
c = g_hash_table_lookup(caps_cache, hash);
if (c) {
char *f = g_strdup(feature);
g_hash_table_replace(c->features, f, f);
}
}
/* If hash is verified, then bare jid is ignored.
* If there is no globally verified hash, and bare jid is not null,
* then local storage for that jid will be checked */
int caps_has_feature(const char *hash, char *feature, char *bjid)
{
caps_t *c = NULL;
if (!hash || !feature)
return 0;
c = g_hash_table_lookup(caps_cache, hash);
if (!c && bjid) {
char *key = g_strdup_printf("%s/#%s", bjid, hash);
c = g_hash_table_lookup(caps_cache, key);
g_free(key);
}
if (c)
return (g_hash_table_lookup(c->features, feature) != NULL);
return 0;
}
static GFunc _foreach_function;
void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data)
{
// GFunc func = (GFunc)user_data;
_foreach_function(value, user_data);
}
void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data)
{
caps_t *c;
if (!hash)
return;
c = g_hash_table_lookup(caps_cache, hash);
if (!c)
return;
_foreach_function = func;
g_hash_table_foreach(c->features, _caps_foreach_helper, user_data);
}
// Generates the sha1 hash for the special capability "" and returns it
const char *caps_generate(void)
{
GList *features, *langs;
GChecksum *sha1;
guint8 digest[20];
gsize digest_size = 20;
gchar *hash, *old_hash = NULL;
caps_t *old_caps, *c;
gpointer key;
if (!g_hash_table_lookup_extended(caps_cache, "", &key, (gpointer *)&c))
return NULL;
g_hash_table_steal(caps_cache, "");
g_free(key);
sha1 = g_checksum_new(G_CHECKSUM_SHA1);
langs = g_hash_table_get_keys(c->identities);
langs = g_list_sort(langs, _strcmp_sort);
{
identity_t *i;
GList *lang;
char *identity_S;
for (lang=langs; lang; lang=lang->next) {
i = g_hash_table_lookup(c->identities, lang->data);
identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
(char *)lang->data, i->name ? i->name : "");
g_checksum_update(sha1, (guchar *)identity_S, -1);
g_free(identity_S);
}
}
g_list_free(langs);
features = g_hash_table_get_values(c->features);
features = g_list_sort(features, _strcmp_sort);
{
GList *feature;
for (feature=features; feature; feature=feature->next) {
g_checksum_update(sha1, feature->data, -1);
g_checksum_update(sha1, (guchar *)"<", -1);
}
}
g_list_free(features);
g_checksum_get_digest(sha1, digest, &digest_size);
hash = g_base64_encode(digest, digest_size);
g_checksum_free(sha1);
g_hash_table_lookup_extended(caps_cache, hash,
(gpointer *)&old_hash, (gpointer *)&old_caps);
g_hash_table_insert(caps_cache, hash, c);
if (old_hash)
return old_hash;
else
return hash;
}
gboolean caps_verify(const char *hash, char *function)
{
GList *features, *langs, *forms;
GChecksum *checksum;
guint8 digest[20];
gsize digest_size = 20;
gchar *local_hash;
gboolean match = FALSE;
caps_t *c = g_hash_table_lookup(caps_cache, hash);
if (!g_strcmp0(function, "sha-1")) {
checksum = g_checksum_new(G_CHECKSUM_SHA1);
} else if (!g_strcmp0(function, "md5")) {
checksum = g_checksum_new(G_CHECKSUM_MD5);
digest_size = 16;
} else
return FALSE;
langs = g_hash_table_get_keys(c->identities);
langs = g_list_sort(langs, _strcmp_sort);
{
identity_t *i;
GList *lang;
char *identity_S;
for (lang=langs; lang; lang=lang->next) {
i = g_hash_table_lookup(c->identities, lang->data);
identity_S = g_strdup_printf("%s/%s/%s/%s<", i->category, i->type,
(char *)lang->data, i->name ? i->name : "");
g_checksum_update(checksum, (guchar *)identity_S, -1);
g_free(identity_S);
}
}
g_list_free(langs);
features = g_hash_table_get_values(c->features);
features = g_list_sort(features, _strcmp_sort);
{
GList *feature;
for (feature=features; feature; feature=feature->next) {
g_checksum_update(checksum, feature->data, -1);
g_checksum_update(checksum, (guchar *)"<", -1);
}
}
g_list_free(features);
forms = g_hash_table_get_keys(c->forms);
forms = g_list_sort(forms, _strcmp_sort);
{
dataform_t *d;
GList *form, *fields;
for (form=forms; form; form=form->next) {
d = g_hash_table_lookup(c->forms, form->data);
g_checksum_update(checksum, form->data, -1);
g_checksum_update(checksum, (guchar *)"<", -1);
fields = g_hash_table_get_keys(d->fields);
fields = g_list_sort(fields, _strcmp_sort);
{
GList *field;
GList *values;
for (field=fields; field; field=field->next) {
g_checksum_update(checksum, field->data, -1);
g_checksum_update(checksum, (guchar *)"<", -1);
values = g_hash_table_lookup(d->fields, field->data);
{
GList *value;
for (value=values; value; value=value->next) {
g_checksum_update(checksum, value->data, -1);
g_checksum_update(checksum, (guchar *)"<", -1);
}
}
}
}
g_list_free(fields);
}
}
g_list_free(forms);
g_checksum_get_digest(checksum, digest, &digest_size);
local_hash = g_base64_encode(digest, digest_size);
g_checksum_free(checksum);
match = !g_strcmp0(hash, local_hash);
g_free(local_hash);
return match;
}
static gchar* caps_get_filename(const char* hash)
{
gchar *hash_fs;
gchar *dir = (gchar *) settings_opt_get ("caps_directory");
gchar *file = NULL;
if (!dir)
goto caps_filename_return;
hash_fs = g_strdup (hash);
{
const gchar *valid_fs =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=";
g_strcanon(hash_fs, valid_fs, '-');
}
dir = expand_filename (dir);
file = g_strdup_printf ("%s/%s.ini", dir, hash_fs);
g_free(dir);
g_free(hash_fs);
caps_filename_return:
return file;
}
/* Store capabilities set in GKeyFile. To be used with verified hashes only */
void caps_copy_to_persistent(const char* hash, char* xml)
{
gchar *file;
GList *features, *langs, *forms;
GKeyFile *key_file;
caps_t *c;
int fd;
g_free (xml);
c = g_hash_table_lookup (caps_cache, hash);
if (!c)
goto caps_copy_return;
file = caps_get_filename (hash);
if (!file)
goto caps_copy_return;
fd = open (file, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
if (fd == -1)
goto caps_copy_exists;
key_file = g_key_file_new ();
g_key_file_set_comment (key_file, NULL, NULL,
"This is autogenerated file. Please do not modify.",
NULL);
langs = g_hash_table_get_keys (c->identities);
{
identity_t *i;
GList *lang;
gchar *group;
for (lang=langs; lang; lang=lang->next) {
i = g_hash_table_lookup (c->identities, lang->data);
group = g_strdup_printf("identity_%s", (gchar *)lang->data);
g_key_file_set_string (key_file, group, "category", i->category);
g_key_file_set_string (key_file, group, "type", i->type);
g_key_file_set_string (key_file, group, "name", i->name);
g_free (group);
}
}
g_list_free (langs);
features = g_hash_table_get_values (c->features);
{
GList *feature;
gchar **string_list;
gint i;
i = g_list_length (features);
string_list = g_new (gchar*, i + 1);
i = 0;
for (feature=features; feature; feature=feature->next) {
string_list[i] = g_strdup(feature->data);
++i;
}
string_list[i] = NULL;
g_key_file_set_string_list (key_file, "features", "features",
(const gchar**)string_list, i);
g_strfreev (string_list);
}
g_list_free (features);
forms = g_hash_table_get_keys(c->forms);
{
dataform_t *d;
GList *form, *fields;
gchar *group;
for (form=forms; form; form=form->next) {
d = g_hash_table_lookup (c->forms, form->data);
group = g_strdup_printf ("form_%s", (gchar *)form->data);
fields = g_hash_table_get_keys(d->fields);
{
GList *field;
GList *values;
for (field=fields; field; field=field->next) {
values = g_hash_table_lookup (d->fields, field->data);
{
GList *value;
gchar **string_list;
gint i;
i = g_list_length (values);
string_list = g_new (gchar*, i + 1);
i = 0;
for (value=values; value; value=value->next) {
string_list[i] = g_strdup(value->data);
++i;
}
string_list[i] = NULL;
g_key_file_set_string_list (key_file, group, field->data,
(const gchar**)string_list, i);
g_strfreev (string_list);
}
}
}
g_list_free(fields);
g_free (group);
}
}
g_list_free (forms);
{
gchar *data;
gsize length;
data = g_key_file_to_data (key_file, &length, NULL);
write (fd, data, length);
g_free(data);
close (fd);
}
g_key_file_free(key_file);
caps_copy_exists:
g_free(file);
caps_copy_return:
return;
}
/* Restore capabilities from GKeyFile. Hash is not verified afterwards */
gboolean caps_restore_from_persistent (const char* hash)
{
gchar *file;
GKeyFile *key_file;
gchar **groups, **group;
gboolean restored = FALSE;
file = caps_get_filename (hash);
if (!file)
goto caps_restore_no_file;
key_file = g_key_file_new ();
if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, NULL))
goto caps_restore_bad_file;
caps_add(hash);
groups = g_key_file_get_groups (key_file, NULL);
for (group = groups; *group; ++group) {
if (!g_strcmp0(*group, "features")) {
gchar **features, **feature;
features = g_key_file_get_string_list (key_file, *group, "features",
NULL, NULL);
for (feature = features; *feature; ++feature) {
caps_add_feature(hash, *feature);
}
g_strfreev (features);
} else if (g_str_has_prefix (*group, "identity_")) {
gchar *category, *type, *name, *lang;
category = g_key_file_get_string(key_file, *group, "category", NULL);
type = g_key_file_get_string(key_file, *group, "type", NULL);
name = g_key_file_get_string(key_file, *group, "name", NULL);
lang = *group + 9; /* "identity_" */
caps_add_identity(hash, category, name, type, lang);
g_free(category);
g_free(type);
g_free(name);
} else if (g_str_has_prefix (*group, "form_")) {
gchar *formtype;
gchar **fields, **field;
formtype = *group + 5; /* "form_" */
caps_add_dataform (hash, formtype);
fields = g_key_file_get_keys(key_file, *group, NULL, NULL);
for (field = fields; *field; ++field) {
gchar **values, **value;
values = g_key_file_get_string_list (key_file, *group, *field,
NULL, NULL);
for (value = values; *value; ++value) {
caps_add_dataform_field (hash, formtype, *field, *value);
}
g_strfreev (values);
}
g_strfreev (fields);
}
}
g_strfreev(groups);
restored = TRUE;
caps_restore_bad_file:
g_key_file_free (key_file);
g_free (file);
caps_restore_no_file:
return restored;
}
/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */