about summary refs log tree commit diff stats
path: root/src/xmpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmpp')
-rw-r--r--src/xmpp/capabilities.c158
-rw-r--r--src/xmpp/capabilities.h8
-rw-r--r--src/xmpp/iq.c271
-rw-r--r--src/xmpp/presence.c42
-rw-r--r--src/xmpp/stanza.c45
-rw-r--r--src/xmpp/stanza.h2
-rw-r--r--src/xmpp/xmpp.h2
7 files changed, 309 insertions, 219 deletions
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index 606bd64b..30f5aef8 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -61,62 +61,15 @@ caps_init(void)
 }
 
 void
-caps_add(const char * const caps_str, const char * const category,
-    const char * const type, const char * const name,
-    const char * const software, const char * const software_version,
-    const char * const os, const char * const os_version,
-    GSList *features)
+caps_add(const char * const ver, Capabilities *caps)
 {
-    Capabilities *new_caps = malloc(sizeof(struct capabilities_t));
-
-    if (category != NULL) {
-        new_caps->category = strdup(category);
-    } else {
-        new_caps->category = NULL;
-    }
-    if (type != NULL) {
-        new_caps->type = strdup(type);
-    } else {
-        new_caps->type = NULL;
-    }
-    if (name != NULL) {
-        new_caps->name = strdup(name);
-    } else {
-        new_caps->name = NULL;
-    }
-    if (software != NULL) {
-        new_caps->software = strdup(software);
-    } else {
-        new_caps->software = NULL;
-    }
-    if (software_version != NULL) {
-        new_caps->software_version = strdup(software_version);
-    } else {
-        new_caps->software_version = NULL;
-    }
-    if (os != NULL) {
-        new_caps->os = strdup(os);
-    } else {
-        new_caps->os = NULL;
-    }
-    if (os_version != NULL) {
-        new_caps->os_version = strdup(os_version);
-    } else {
-        new_caps->os_version = NULL;
-    }
-    if (features != NULL) {
-        new_caps->features = features;
-    } else {
-        new_caps->features = NULL;
-    }
-
-    g_hash_table_insert(capabilities, strdup(caps_str), new_caps);
+    g_hash_table_insert(capabilities, strdup(ver), caps);
 }
 
 gboolean
-caps_contains(const char * const caps_str)
+caps_contains(const char * const caps_ver)
 {
-    return (g_hash_table_lookup(capabilities, caps_str) != NULL);
+    return (g_hash_table_lookup(capabilities, caps_ver) != NULL);
 }
 
 static Capabilities *
@@ -228,6 +181,109 @@ caps_create_sha1_str(xmpp_stanza_t * const query)
     return result;
 }
 
+Capabilities *
+caps_create(xmpp_stanza_t *query)
+{
+    const char *category = NULL;
+    const char *type = NULL;
+    const char *name = NULL;
+    const char *software = NULL;
+    const char *software_version = NULL;
+    const char *os = NULL;
+    const char *os_version = NULL;
+    GSList *features = NULL;
+
+    xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity");
+    if (identity != NULL) {
+        category = xmpp_stanza_get_attribute(identity, "category");
+        type = xmpp_stanza_get_attribute(identity, "type");
+        name = xmpp_stanza_get_attribute(identity, "name");
+    }
+
+    xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
+    if (softwareinfo != NULL) {
+        DataForm *form = form_create(softwareinfo);
+        FormField *formField = NULL;
+
+        char *form_type = form_get_form_type_field(form);
+        if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) {
+            GSList *field = form->fields;
+            while (field != NULL) {
+                formField = field->data;
+                if (formField->values != NULL) {
+                    if (strcmp(formField->var, "software") == 0) {
+                        software = formField->values->data;
+                    } else if (strcmp(formField->var, "software_version") == 0) {
+                        software_version = formField->values->data;
+                    } else if (strcmp(formField->var, "os") == 0) {
+                        os = formField->values->data;
+                    } else if (strcmp(formField->var, "os_version") == 0) {
+                        os_version = formField->values->data;
+                    }
+                }
+                field = g_slist_next(field);
+            }
+        }
+
+        form_destroy(form);
+    }
+
+    xmpp_stanza_t *child = xmpp_stanza_get_children(query);
+    while (child != NULL) {
+        if (g_strcmp0(xmpp_stanza_get_name(child), "feature") == 0) {
+            features = g_slist_append(features, strdup(xmpp_stanza_get_attribute(child, "var")));
+        }
+
+        child = xmpp_stanza_get_next(child);
+    }
+
+    Capabilities *new_caps = malloc(sizeof(struct capabilities_t));
+
+    if (category != NULL) {
+        new_caps->category = strdup(category);
+    } else {
+        new_caps->category = NULL;
+    }
+    if (type != NULL) {
+        new_caps->type = strdup(type);
+    } else {
+        new_caps->type = NULL;
+    }
+    if (name != NULL) {
+        new_caps->name = strdup(name);
+    } else {
+        new_caps->name = NULL;
+    }
+    if (software != NULL) {
+        new_caps->software = strdup(software);
+    } else {
+        new_caps->software = NULL;
+    }
+    if (software_version != NULL) {
+        new_caps->software_version = strdup(software_version);
+    } else {
+        new_caps->software_version = NULL;
+    }
+    if (os != NULL) {
+        new_caps->os = strdup(os);
+    } else {
+        new_caps->os = NULL;
+    }
+    if (os_version != NULL) {
+        new_caps->os_version = strdup(os_version);
+    } else {
+        new_caps->os_version = NULL;
+    }
+    if (features != NULL) {
+        new_caps->features = features;
+    } else {
+        new_caps->features = NULL;
+    }
+
+    return new_caps;
+}
+
+
 xmpp_stanza_t *
 caps_create_query_response_stanza(xmpp_ctx_t * const ctx)
 {
diff --git a/src/xmpp/capabilities.h b/src/xmpp/capabilities.h
index 39b87eb0..71a1edb5 100644
--- a/src/xmpp/capabilities.h
+++ b/src/xmpp/capabilities.h
@@ -40,12 +40,10 @@
 #include "xmpp/xmpp.h"
 
 void caps_init(void);
-void caps_add(const char * const caps_str, const char * const category,
-    const char * const type, const char * const name,
-    const char * const software, const char * const software_version,
-    const char * const os, const char * const os_version, GSList *features);
-gboolean caps_contains(const char * const caps_str);
+void caps_add(const char * const ver, Capabilities *caps);
+gboolean caps_contains(const char * const caps_ver);
 char* caps_create_sha1_str(xmpp_stanza_t * const query);
 xmpp_stanza_t* caps_create_query_response_stanza(xmpp_ctx_t * const ctx);
+Capabilities* caps_create(xmpp_stanza_t *query);
 
 #endif
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 822e552d..2a228d74 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -66,7 +66,7 @@ static int _version_get_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _disco_info_get_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
-static int _disco_info_result_handler(xmpp_conn_t * const conn,
+static int _disco_info_response_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _version_result_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
@@ -84,6 +84,8 @@ static int _manual_pong_handler(xmpp_conn_t *const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _ping_timed_handler(xmpp_conn_t * const conn,
     void * const userdata);
+static int _caps_response_handler(xmpp_conn_t *const conn,
+    xmpp_stanza_t * const stanza, void * const userdata);
 
 void
 iq_add_handlers(void)
@@ -94,7 +96,6 @@ iq_add_handlers(void)
     HANDLE(NULL,                STANZA_TYPE_ERROR,  _error_handler);
 
     HANDLE(XMPP_NS_DISCO_INFO,  STANZA_TYPE_GET,    _disco_info_get_handler);
-    HANDLE(XMPP_NS_DISCO_INFO,  STANZA_TYPE_RESULT, _disco_info_result_handler);
 
     HANDLE(XMPP_NS_DISCO_ITEMS, STANZA_TYPE_GET,    _disco_items_get_handler);
     HANDLE(XMPP_NS_DISCO_ITEMS, STANZA_TYPE_RESULT, _disco_items_result_handler);
@@ -142,7 +143,38 @@ _iq_disco_info_request(gchar *jid)
 {
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, "discoinforeq", jid, NULL);
+    char *id = create_unique_id("disco_info");
+    xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, jid, NULL);
+
+    xmpp_id_handler_add(conn, _disco_info_response_handler, id, NULL);
+
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+static void
+_iq_send_caps_request(const char * const to, const char * const id,
+    const char * const node, const char * const ver)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+
+    if (!node) {
+        log_error("Could not create caps request, no node");
+        return;
+    }
+    if (!ver) {
+        log_error("Could not create caps request, no ver");
+        return;
+    }
+
+    GString *node_str = g_string_new("");
+    g_string_printf(node_str, "%s#%s", node, ver);
+    xmpp_stanza_t *iq = stanza_create_disco_info_iq(ctx, id, to, node_str->str);
+    g_string_free(node_str, TRUE);
+
+    xmpp_id_handler_add(conn, _caps_response_handler, id, NULL);
+
     xmpp_send(conn, iq);
     xmpp_stanza_release(iq);
 }
@@ -304,6 +336,51 @@ _pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
 }
 
 static int
+_caps_response_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+
+    if (id) {
+        log_info("Capabilities response handler fired for id %s", id);
+    } else {
+        log_info("Capabilities response handler fired");
+    }
+
+    char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE);
+    if (node == NULL) {
+        log_warning("No node attribute found");
+        return 0;
+    }
+
+    // validate sha1
+    gchar **split = g_strsplit(node, "#", -1);
+    char *given_sha1 = split[1];
+    char *generated_sha1 = caps_create_sha1_str(query);
+
+    if (g_strcmp0(given_sha1, generated_sha1) != 0) {
+        log_warning("Generated sha-1 does not match given:");
+        log_warning("Generated : %s", generated_sha1);
+        log_warning("Given     : %s", given_sha1);
+    } else {
+        log_info("Valid SHA-1 hash found: %s", given_sha1);
+
+        if (caps_contains(given_sha1)) {
+            log_info("Capabilties cached");
+        } else {
+            log_info("Capabilities not cached, storing");
+            Capabilities *capabilities = caps_create(query);
+            caps_add(given_sha1, capabilities);
+        }
+    }
+
+    g_free(generated_sha1);
+    g_strfreev(split);
+    return 0;
+}
+
+static int
 _manual_pong_handler(xmpp_conn_t *const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
@@ -710,171 +787,60 @@ _item_destroy(DiscoItem *item)
 }
 
 static int
-_disco_info_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+_disco_info_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
     void * const userdata)
 {
     log_debug("Received diso#info response");
-    const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
     const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
 
-    if (g_strcmp0(id, "discoinforeq") == 0) {
-
-        xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+    xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
 
-        if (query != NULL) {
-            xmpp_stanza_t *child = xmpp_stanza_get_children(query);
-            GSList *identities = NULL;
-            GSList *features = NULL;
-            while (child != NULL) {
-                const char *stanza_name = xmpp_stanza_get_name(child);
-                if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) {
-                    const char *var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR);
-                    if (var != NULL) {
-                        features = g_slist_append(features, strdup(var));
+    if (query != NULL) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(query);
+        GSList *identities = NULL;
+        GSList *features = NULL;
+        while (child != NULL) {
+            const char *stanza_name = xmpp_stanza_get_name(child);
+            if (g_strcmp0(stanza_name, STANZA_NAME_FEATURE) == 0) {
+                const char *var = xmpp_stanza_get_attribute(child, STANZA_ATTR_VAR);
+                if (var != NULL) {
+                    features = g_slist_append(features, strdup(var));
+                }
+            } else if (g_strcmp0(stanza_name, STANZA_NAME_IDENTITY) == 0) {
+                const char *name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
+                const char *type = xmpp_stanza_get_attribute(child, STANZA_ATTR_TYPE);
+                const char *category = xmpp_stanza_get_attribute(child, STANZA_ATTR_CATEGORY);
+
+                if ((name != NULL) || (category != NULL) || (type != NULL)) {
+                    DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t));
+
+                    if (name != NULL) {
+                        identity->name = strdup(name);
+                    } else {
+                        identity->name = NULL;
                     }
-                } else if (g_strcmp0(stanza_name, STANZA_NAME_IDENTITY) == 0) {
-                    const char *name = xmpp_stanza_get_attribute(child, STANZA_ATTR_NAME);
-                    const char *type = xmpp_stanza_get_attribute(child, STANZA_ATTR_TYPE);
-                    const char *category = xmpp_stanza_get_attribute(child, STANZA_ATTR_CATEGORY);
-
-                    if ((name != NULL) || (category != NULL) || (type != NULL)) {
-                        DiscoIdentity *identity = malloc(sizeof(struct disco_identity_t));
-
-                        if (name != NULL) {
-                            identity->name = strdup(name);
-                        } else {
-                            identity->name = NULL;
-                        }
-                        if (category != NULL) {
-                            identity->category = strdup(category);
-                        } else {
-                            identity->category = NULL;
-                        }
-                        if (type != NULL) {
-                            identity->type = strdup(type);
-                        } else {
-                            identity->type = NULL;
-                        }
-
-                        identities = g_slist_append(identities, identity);
+                    if (category != NULL) {
+                        identity->category = strdup(category);
+                    } else {
+                        identity->category = NULL;
                     }
-                }
-
-                child = xmpp_stanza_get_next(child);
-            }
-
-            handle_disco_info(from, identities, features);
-            g_slist_free_full(features, free);
-            g_slist_free_full(identities, (GDestroyNotify)_identity_destroy);
-        }
-    } else if ((id != NULL) && (g_str_has_prefix(id, "capsreq"))) {
-        log_debug("Response to query: %s", id);
-        xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
-        char *node = xmpp_stanza_get_attribute(query, STANZA_ATTR_NODE);
-        if (node == NULL) {
-            return 1;
-        }
-
-        char *caps_key = NULL;
-
-        // xep-0115
-        if (g_strcmp0(id, "capsreq") == 0) {
-            log_debug("xep-0115 supported capabilities");
-            caps_key = strdup(node);
-
-            // validate sha1
-            gchar **split = g_strsplit(node, "#", -1);
-            char *given_sha1 = split[1];
-            char *generated_sha1 = caps_create_sha1_str(query);
-
-            if (g_strcmp0(given_sha1, generated_sha1) != 0) {
-                log_info("Generated sha-1 does not match given:");
-                log_info("Generated : %s", generated_sha1);
-                log_info("Given     : %s", given_sha1);
-                g_free(generated_sha1);
-                g_strfreev(split);
-                free(caps_key);
-
-                return 1;
-            }
-            g_free(generated_sha1);
-            g_strfreev(split);
-
-        // non supported hash, or legacy caps
-        } else {
-            log_debug("Unsupported hash, or legacy capabilities");
-            caps_key = strdup(id + 8);
-            log_debug("Caps key: %s", caps_key);
-        }
-
-        // already cached
-        if (caps_contains(caps_key)) {
-            log_info("Client info already cached.");
-            free(caps_key);
-            return 1;
-        }
-
-        log_debug("Client info not cached");
-
-        const char *category = NULL;
-        const char *type = NULL;
-        const char *name = NULL;
-        const char *software = NULL;
-        const char *software_version = NULL;
-        const char *os = NULL;
-        const char *os_version = NULL;
-        GSList *features = NULL;
-
-        xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity");
-        if (identity != NULL) {
-            category = xmpp_stanza_get_attribute(identity, "category");
-            type = xmpp_stanza_get_attribute(identity, "type");
-            name = xmpp_stanza_get_attribute(identity, "name");
-        }
-
-        xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
-        if (softwareinfo != NULL) {
-            DataForm *form = form_create(softwareinfo);
-            FormField *formField = NULL;
-
-            char *form_type = form_get_form_type_field(form);
-            if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) {
-                GSList *field = form->fields;
-                while (field != NULL) {
-                    formField = field->data;
-                    if (formField->values != NULL) {
-                        if (strcmp(formField->var, "software") == 0) {
-                            software = formField->values->data;
-                        } else if (strcmp(formField->var, "software_version") == 0) {
-                            software_version = formField->values->data;
-                        } else if (strcmp(formField->var, "os") == 0) {
-                            os = formField->values->data;
-                        } else if (strcmp(formField->var, "os_version") == 0) {
-                            os_version = formField->values->data;
-                        }
+                    if (type != NULL) {
+                        identity->type = strdup(type);
+                    } else {
+                        identity->type = NULL;
                     }
-                    field = g_slist_next(field);
-                }
-            }
-
-            form_destroy(form);
-        }
 
-        xmpp_stanza_t *child = xmpp_stanza_get_children(query);
-        while (child != NULL) {
-            if (g_strcmp0(xmpp_stanza_get_name(child), "feature") == 0) {
-                features = g_slist_append(features, strdup(xmpp_stanza_get_attribute(child, "var")));
+                    identities = g_slist_append(identities, identity);
+                }
             }
 
             child = xmpp_stanza_get_next(child);
         }
 
-        caps_add(caps_key, category, type, name, software, software_version,
-            os, os_version, features);
-
-        free(caps_key);
+        handle_disco_info(from, identities, features);
+        g_slist_free_full(features, free);
+        g_slist_free_full(identities, (GDestroyNotify)_identity_destroy);
     }
-
     return 1;
 }
 
@@ -941,4 +907,5 @@ iq_init_module(void)
     iq_request_room_config_form = _iq_request_room_config_form;
     iq_room_config_cancel = _iq_room_config_cancel;
     iq_submit_room_config = _iq_submit_room_config;
+    iq_send_caps_request = _iq_send_caps_request;
 }
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index cd83cf5d..5e97b963 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -581,12 +581,46 @@ _available_handler(xmpp_conn_t * const conn,
         free(priority_str);
     }
 
-    // get capabilities key
-    char *caps_key = NULL;
+    // send disco info for capabilities, if not cached
     if (stanza_contains_caps(stanza)) {
-        caps_key = _get_caps_key(stanza);
+        log_info("Presence contains capabilities.");
+
+        char *hash = stanza_caps_get_hash(stanza);
+
+        // hash supported xep-0115
+        if (g_strcmp0(hash, "sha-1") == 0) {
+            log_info("Hash %s supported");
+
+            char *ver = stanza_get_caps_ver(stanza);
+            if (caps_contains(ver)) {
+                log_info("Capabilities cached");
+            } else {
+                log_info("Capabilities not cached, sending service discovery request");
+                char *node = stanza_caps_get_node(stanza);
+                char *id = create_unique_id("caps");
+
+                iq_send_caps_request(from, id, node, ver);
+
+                // send service discovery request
+                // with id handler to validate response,
+                // generate hash,
+                // if match, cache against hash
+            }
+
+        // no hash, or not supported
+        } else {
+            if (hash) {
+                log_info("Hash %s not supported, not sending service discovery request");
+                // send service discovery request, cache against from full jid
+            } else {
+                log_info("No hash specified, not sending service discovery request");
+                // do legacy
+            }
+        }
     }
 
+    char *caps_key = strdup("hello");
+
     // create Resource
     Resource *resource = NULL;
     resource_presence_t presence = resource_presence_from_string(show_str);
@@ -648,8 +682,6 @@ _get_caps_key(xmpp_stanza_t * const stanza)
     char *caps_key = NULL;
     char *id = NULL;
 
-    log_debug("Presence contains capabilities.");
-
     if (node == NULL) {
         return NULL;
     }
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 118bffb7..cca07539 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -973,11 +973,12 @@ stanza_contains_caps(xmpp_stanza_t * const stanza)
 {
     xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C);
 
-    if (caps == NULL) {
+    if (!caps) {
         return FALSE;
     }
 
-    if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) {
+    char *ns = xmpp_stanza_get_ns(caps);
+    if (g_strcmp0(ns, STANZA_NS_CAPS) != 0) {
         return FALSE;
     }
 
@@ -989,18 +990,50 @@ stanza_caps_get_hash(xmpp_stanza_t * const stanza)
 {
     xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C);
 
-    if (caps == NULL) {
+    if (!caps) {
         return NULL;
     }
 
-    if (strcmp(xmpp_stanza_get_ns(caps), STANZA_NS_CAPS) != 0) {
+    char *ns = xmpp_stanza_get_ns(caps);
+    if (g_strcmp0(ns, STANZA_NS_CAPS) != 0) {
+        return NULL;
+    }
+
+    return xmpp_stanza_get_attribute(caps, STANZA_ATTR_HASH);
+}
+
+char *
+stanza_caps_get_node(xmpp_stanza_t * const stanza)
+{
+    xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C);
+
+    if (!caps) {
         return NULL;
     }
 
-    char *result = xmpp_stanza_get_attribute(caps, STANZA_ATTR_HASH);
+    char *ns = xmpp_stanza_get_ns(caps);
+    if (g_strcmp0(ns, STANZA_NS_CAPS) != 0) {
+        return NULL;
+    }
+
+    return xmpp_stanza_get_attribute(caps, STANZA_ATTR_NODE);
+}
 
-    return result;
+char *
+stanza_get_caps_ver(xmpp_stanza_t * const stanza)
+{
+    xmpp_stanza_t *caps = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_C);
+
+    if (!caps) {
+        return NULL;
+    }
+
+    char *ns = xmpp_stanza_get_ns(caps);
+    if (g_strcmp0(ns, STANZA_NS_CAPS) != 0) {
+        return NULL;
+    }
 
+    return xmpp_stanza_get_attribute(caps, STANZA_ATTR_VER);
 }
 
 char *
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 155044f2..a98c8c22 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -209,6 +209,8 @@ int stanza_get_idle_time(xmpp_stanza_t * const stanza);
 char * stanza_get_caps_str(xmpp_stanza_t * const stanza);
 gboolean stanza_contains_caps(xmpp_stanza_t * const stanza);
 char * stanza_caps_get_hash(xmpp_stanza_t * const stanza);
+char * stanza_get_caps_ver(xmpp_stanza_t * const stanza);
+char * stanza_caps_get_node(xmpp_stanza_t * const stanza);
 
 DataForm * stanza_create_form(xmpp_stanza_t * const stanza);
 void stanza_destroy_form(DataForm *form);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 3b0b8156..c05a3b21 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -189,6 +189,8 @@ void (*iq_request_room_config_form)(const char * const room_jid);
 void (*iq_submit_room_config)(const char * const room, DataForm *form);
 void (*iq_room_config_cancel)(const char * const room_jid);
 void (*iq_send_ping)(const char * const target);
+void (*iq_send_caps_request)(const char * const to, const char * const id,
+    const char * const node, const char * const ver);
 
 // caps functions
 Capabilities* (*caps_get)(const char * const caps_str);