/* xmpp-discoinfo.c - Hold XMPP discovery info * * Copyright 2008 OpenMoko, Inc. * Authored by Daniel Willmann * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include G_DEFINE_TYPE(XmppDiscoinfo, xmpp_discoinfo, G_TYPE_OBJECT); #define XMPP_DISCOINFO_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), XMPP_TYPE_DISCOINFO, XmppDiscoinfoPrivate)) typedef struct _XmppDiscoinfoPrivate XmppDiscoinfoPrivate; struct _XmppDiscoinfoPrivate { GList *identities; GList *features; }; static void xmpp_discoinfo_ident_cleanup(struct XmppDiscoinfoIdentity *ident, gpointer data) { g_free(ident->category); g_free(ident->type); g_free(ident->name); ident->category = NULL; ident->type = NULL; ident->name = NULL; g_free(ident); } static void xmpp_discoinfo_feat_cleanup(struct XmppDiscoinfoFeature *feat, gpointer data) { g_free(feat->var); feat->var = NULL; g_free(feat); } static void xmpp_discoinfo_finalize(GObject *obj) { GError *error = NULL; XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(obj); if (priv->identities) { g_list_foreach(priv->identities, xmpp_discoinfo_ident_cleanup, NULL); g_list_free(priv->identities); priv->identities = NULL; } if (priv->features) { g_list_foreach(priv->features, xmpp_discoinfo_feat_cleanup, NULL); g_list_free(priv->features); priv->features = NULL; } ((GObjectClass *) xmpp_discoinfo_parent_class)->finalize(obj); } static void xmpp_discoinfo_class_init(XmppDiscoinfoClass *klass) { GObjectClass *o_class = (GObjectClass *) klass; //o_class->dispose = xmpp_discoinfo_dispose; o_class->finalize = xmpp_discoinfo_finalize; g_type_class_add_private(klass, sizeof(XmppDiscoinfoPrivate)); } static void xmpp_discoinfo_init(XmppDiscoinfo *discoinfo) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); priv->identities = NULL; priv->features = NULL; } XmppDiscoinfo *xmpp_discoinfo_new(GError **error) { XmppDiscoinfo *discoinfo; XmppDiscoinfoPrivate *priv; discoinfo = g_object_new(XMPP_TYPE_DISCOINFO, NULL); priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); return discoinfo; } /* Create an identity/feature list from a */ XmppDiscoinfo *xmpp_discoinfo_new_from_msg_node(LmMessageNode *node, GError **error) { XmppDiscoinfo *discoinfo; XmppDiscoinfoPrivate *priv; LmMessageNode *temp; char *category, *type, *name, *var; discoinfo = g_object_new(XMPP_TYPE_DISCOINFO, NULL); priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); if (!strcmp(lm_message_node_get_attribute(node, "xmlns"), XMPP_NS_DISCO_INFO)) { /* Get the identities */ for (temp = lm_message_node_get_child(node, "identity"); temp!=NULL; temp=temp->next) { if (!strcmp(temp->name, "identity")) { category = lm_message_node_get_attribute(temp, "category"); type = lm_message_node_get_attribute(temp, "type"); name = lm_message_node_get_attribute(temp, "name"); xmpp_discoinfo_add_identity(discoinfo, category, type, name); } } /* Get the features */ for (temp = lm_message_node_get_child(node, "feature"); temp!=NULL; temp=temp->next) { if (!strcmp(temp->name, "feature")) { var = lm_message_node_get_attribute(temp, "var"); xmpp_discoinfo_add_feature(discoinfo, var); } } } return discoinfo; } void xmpp_discoinfo_ident_to_node(struct XmppDiscoinfoIdentity *ident, LmMessageNode *node) { LmMessageNode *child; child = lm_message_node_add_child(node, "identity", NULL); lm_message_node_set_attributes(child, "category", ident->category, "type", ident->type, "name", ident->name, NULL); } void xmpp_discoinfo_feat_to_node(struct XmppDiscoinfoFeature *feat, LmMessageNode *node) { LmMessageNode *child; child = lm_message_node_add_child(node, "feature", NULL); lm_message_node_set_attribute(child, "var", feat->var); } static gint identcmp (struct XmppDiscoinfoIdentity *a, struct XmppDiscoinfoIdentity *b) { gint result, alen, blen; alen = strlen(a->category); blen = strlen(b->category); // First sort by category if (!(result = memcmp(a->category, b->category, alen>blen?blen:alen))) { if (alen == blen) { // If they're equal sort by type alen = strlen(a->type); blen = strlen(b->type); if (!(result = memcmp(a->type, b->type, alen>blen?blen:alen))) { return alen-blen; } return result; } return alen - blen; } return result; } void xmpp_discoinfo_add_identity(XmppDiscoinfo *discoinfo, const gchar *category, const gchar *type, const gchar *name) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); struct XmppDiscoinfoIdentity *ident; ident = g_new0(struct XmppDiscoinfoIdentity, 1); ident->category = g_strdup(category); ident->type = g_strdup(type); ident->name = g_strdup(name); priv->identities = g_list_insert_sorted(priv->identities, ident, identcmp); } static gint featcmp (struct XmppDiscoinfoFeature *a, struct XmppDiscoinfoFeature *b) { gint result, alen, blen; alen = strlen(a->var); blen = strlen(b->var); // Sort by feature name if (!(result = memcmp(a->var, b->var, alen>blen?blen:alen))) { return alen-blen; } return result; } void xmpp_discoinfo_add_feature(XmppDiscoinfo *discoinfo, const gchar *var) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); struct XmppDiscoinfoFeature *feat; feat = g_new0(struct XmppDiscoinfoFeature, 1); feat->var = g_strdup(var); priv->features = g_list_insert_sorted(priv->features, feat, featcmp); } GList *xmpp_discoinfo_get_identities(XmppDiscoinfo *discoinfo) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); return priv->identities; } GList *xmpp_discoinfo_get_features(XmppDiscoinfo *discoinfo) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); return priv->features; } static void xmpp_discoinfo_hash_identities(struct XmppDiscoinfoIdentity *identity, GString *s) { g_string_append_printf(s, "%s/%s<", identity->category, identity->type); } static void xmpp_discoinfo_hash_features(struct XmppDiscoinfoFeature *feature, GString *s) { g_string_append_printf(s, "%s<", feature->var); } char *xmpp_discoinfo_get_hash(XmppDiscoinfo *discoinfo, const char *hash_func) { /* FIXME: Support more than just sha-1 */ if (!strcmp(hash_func, "sha-1")) { XmppDiscoinfoPrivate *priv = XMPP_DISCOINFO_GET_PRIVATE(discoinfo); GString *s = g_string_new(""); guchar *s_hashed, *s_base64; SHA1Context hash; /* Generate S as described in XEP-0115 */ g_list_foreach(priv->identities, xmpp_discoinfo_hash_identities, s); g_list_foreach(priv->features, xmpp_discoinfo_hash_features, s); /* Hash S */ SHA1Init (&hash); s_hashed = g_string_free(s, FALSE); SHA1Update(&hash, s_hashed, strlen(s_hashed)); g_free(s_hashed); s_hashed = g_new(uint8_t, SHA1_HASH_SIZE); SHA1Final(&hash, s_hashed); /* Base64 encode hash */ s_base64 = g_base64_encode(s_hashed, SHA1_HASH_SIZE); g_free(s_hashed); return s_base64; } return NULL; }