about summary refs log tree commit diff stats
path: root/src/jabber.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/jabber.c')
-rw-r--r--src/jabber.c258
1 files changed, 250 insertions, 8 deletions
diff --git a/src/jabber.c b/src/jabber.c
index 2d69ebec..244921bf 100644
--- a/src/jabber.c
+++ b/src/jabber.c
@@ -26,6 +26,7 @@
 
 #include <strophe.h>
 
+#include "capabilities.h"
 #include "chat_session.h"
 #include "common.h"
 #include "contact_list.h"
@@ -84,9 +85,16 @@ static int _iq_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _roster_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
+static int _disco_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata);
+static int _disco_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata);
+static int _version_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata);
 static int _presence_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 char * _handle_presence_caps(xmpp_stanza_t * const stanza);
 
 void
 jabber_init(const int disable_tls)
@@ -414,6 +422,16 @@ jabber_update_presence(jabber_presence_t status, const char * const msg,
         xmpp_stanza_add_child(presence, query);
     }
 
+    // add caps
+    xmpp_stanza_t *caps = xmpp_stanza_new(jabber_conn.ctx);
+    xmpp_stanza_set_name(caps, STANZA_NAME_C);
+    xmpp_stanza_set_ns(caps, STANZA_NS_CAPS);
+    xmpp_stanza_set_attribute(caps, STANZA_ATTR_HASH, "sha-1");
+    xmpp_stanza_set_attribute(caps, STANZA_ATTR_NODE, "http://www.profanity.im");
+    xmpp_stanza_t *query = caps_get_query_response_stanza(jabber_conn.ctx);
+    char *sha1 = caps_get_sha1_str(query);
+    xmpp_stanza_set_attribute(caps, STANZA_ATTR_VER, sha1);
+    xmpp_stanza_add_child(presence, caps);
     xmpp_send(jabber_conn.conn, presence);
 
     // send presence for each room
@@ -783,11 +801,24 @@ _iq_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
 {
     char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
+    char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
 
     // handle the initial roster request
-    if ((id != NULL) && (strcmp(id, "roster") == 0)) {
+    if (g_strcmp0(id, "roster") == 0) {
         return _roster_handler(conn, stanza, userdata);
 
+    // handle disco responses
+    } else if ((id != NULL) && (g_str_has_prefix(id, "disco")) &&
+            (g_strcmp0(type, "result") == 0)) {
+        return _disco_response_handler(conn, stanza, userdata);
+
+    // handle disco requests
+    } else if (stanza_is_caps_request(stanza)) {
+        return _disco_request_handler(conn, stanza, userdata);
+
+    } else if (stanza_is_version_request(stanza)) {
+        return _version_request_handler(conn, stanza, userdata);
+
     // handle iq
     } else {
         char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
@@ -877,8 +908,8 @@ _iq_handler(xmpp_conn_t * const conn,
 }
 
 static int
-_roster_handler(xmpp_conn_t * const conn,
-    xmpp_stanza_t * const stanza, void * const userdata)
+_roster_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
 {
     xmpp_stanza_t *query, *item;
     char *type = xmpp_stanza_get_type(stanza);
@@ -921,6 +952,148 @@ _roster_handler(xmpp_conn_t * const conn,
 }
 
 static int
+_version_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
+
+    char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    char *id = xmpp_stanza_get_id(stanza);
+
+    if (from != NULL) {
+        xmpp_stanza_t *response = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(response, STANZA_NAME_IQ);
+        if (id != NULL) {
+            xmpp_stanza_set_id(response, id);
+        }
+        xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from);
+        xmpp_stanza_set_type(response, STANZA_TYPE_RESULT);
+
+        xmpp_stanza_t *query = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+        xmpp_stanza_set_ns(query, STANZA_NS_VERSION);
+
+        xmpp_stanza_t *name = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(name, "name");
+        xmpp_stanza_t *name_txt = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_text(name_txt, "Profanity");
+        xmpp_stanza_add_child(name, name_txt);
+
+        xmpp_stanza_t *version = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(version, "version");
+        xmpp_stanza_t *version_txt = xmpp_stanza_new(ctx);
+        GString *version_str = g_string_new(PACKAGE_VERSION);
+        if (strcmp(PACKAGE_STATUS, "development") == 0) {
+            g_string_append(version_str, "dev");
+        }
+        xmpp_stanza_set_text(version_txt, version_str->str);
+        xmpp_stanza_add_child(version, version_txt);
+
+        xmpp_stanza_add_child(query, name);
+        xmpp_stanza_add_child(query, version);
+        xmpp_stanza_add_child(response, query);
+
+        xmpp_send(conn, response);
+    }
+
+    return 1;
+}
+
+static int
+_disco_request_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    xmpp_ctx_t *ctx = (xmpp_ctx_t *)userdata;
+
+    xmpp_stanza_t *incoming_query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+    char *node_str = xmpp_stanza_get_attribute(incoming_query, STANZA_ATTR_NODE);
+    char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+
+    if (from != NULL && node_str != NULL) {
+        xmpp_stanza_t *response = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(response, STANZA_NAME_IQ);
+        xmpp_stanza_set_id(response, xmpp_stanza_get_id(stanza));
+        xmpp_stanza_set_attribute(response, STANZA_ATTR_TO, from);
+        xmpp_stanza_set_type(response, STANZA_TYPE_RESULT);
+        xmpp_stanza_t *query = caps_get_query_response_stanza(ctx);
+        xmpp_stanza_set_attribute(query, STANZA_ATTR_NODE, node_str);
+        xmpp_stanza_add_child(response, query);
+        xmpp_send(conn, response);
+    }
+
+    return 1;
+}
+
+static int
+_disco_response_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    char *type = xmpp_stanza_get_type(stanza);
+    char *id = xmpp_stanza_get_id(stanza);
+
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        log_error("Roster query failed");
+        return 1;
+    } else {
+        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, "disco") == 0) {
+            caps_key = node;
+
+            // validate sha1
+            gchar **split = g_strsplit(node, "#", -1);
+            char *given_sha1 = split[1];
+            char *generated_sha1 = caps_get_sha1_str(query);
+
+            if (g_strcmp0(given_sha1, generated_sha1) != 0) {
+                log_info("Invalid SHA1 recieved for caps.");
+                return 1;
+            }
+        // non supported hash, or legacy caps
+        } else {
+            caps_key = id + 6;
+        }
+
+        // already cached
+        if (caps_contains(caps_key)) {
+            log_info("Client info already cached.");
+            return 1;
+        }
+
+        xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity");
+
+        if (identity == NULL) {
+            return 1;
+        }
+
+        const char *category = xmpp_stanza_get_attribute(identity, "category");
+        if (category == NULL) {
+            return 1;
+        }
+
+        if (strcmp(category, "client") != 0) {
+            return 1;
+        }
+
+        const char *name = xmpp_stanza_get_attribute(identity, "name");
+        if (name == 0) {
+            return 1;
+        }
+
+        caps_add(caps_key, name);
+
+        return 1;
+    }
+}
+
+static int
 _ping_timed_handler(xmpp_conn_t * const conn, void * const userdata)
 {
     if (jabber_conn.conn_status == JABBER_CONNECTED) {
@@ -974,6 +1147,7 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza)
     } else {
         char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
         char *show_str, *status_str;
+        char *caps_key = _handle_presence_caps(stanza);
 
         xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS);
         if (status != NULL) {
@@ -999,18 +1173,18 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza)
                 show_str = "online";
             }
             if (!muc_get_roster_received(room)) {
-                muc_add_to_roster(room, nick, show_str, status_str);
+                muc_add_to_roster(room, nick, show_str, status_str, caps_key);
             } else {
                 char *old_nick = muc_complete_roster_nick_change(room, nick);
 
                 if (old_nick != NULL) {
-                    muc_add_to_roster(room, nick, show_str, status_str);
+                    muc_add_to_roster(room, nick, show_str, status_str, caps_key);
                     prof_handle_room_member_nick_change(room, old_nick, nick);
                 } else {
                     if (!muc_nick_in_roster(room, nick)) {
-                        prof_handle_room_member_online(room, nick, show_str, status_str);
+                        prof_handle_room_member_online(room, nick, show_str, status_str, caps_key);
                     } else {
-                        prof_handle_room_member_presence(room, nick, show_str, status_str);
+                        prof_handle_room_member_presence(room, nick, show_str, status_str, caps_key);
                     }
                 }
             }
@@ -1023,6 +1197,72 @@ _room_presence_handler(const char * const jid, xmpp_stanza_t * const stanza)
     return 1;
 }
 
+static char *
+_handle_presence_caps(xmpp_stanza_t * const stanza)
+{
+    char *caps_key = NULL;
+    char *node = NULL;
+    char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (stanza_contains_caps(stanza)) {
+        char *hash_type = stanza_caps_get_hash(stanza);
+
+        // xep-0115
+        if (hash_type != NULL) {
+
+            // supported hash
+            if (strcmp(hash_type, "sha-1") == 0) {
+                node = stanza_get_caps_str(stanza);
+                caps_key = node;
+
+                if (node != NULL) {
+                    if (!caps_contains(caps_key)) {
+                        xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, "disco", from, node);
+                        xmpp_send(jabber_conn.conn, iq);
+                        xmpp_stanza_release(iq);
+                    }
+                }
+
+            // unsupported hash
+            } else {
+                node = stanza_get_caps_str(stanza);
+                caps_key = from;
+
+                if (node != NULL) {
+                    if (!caps_contains(caps_key)) {
+                        GString *id = g_string_new("disco_");
+                        g_string_append(id, from);
+                        xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, id->str, from, node);
+                        xmpp_send(jabber_conn.conn, iq);
+                        xmpp_stanza_release(iq);
+                        g_string_free(id, TRUE);
+                    }
+                }
+            }
+
+            return strdup(caps_key);
+
+        //ignore or handle legacy caps
+        } else {
+            node = stanza_get_caps_str(stanza);
+            caps_key = from;
+
+            if (node != NULL) {
+                if (!caps_contains(caps_key)) {
+                    GString *id = g_string_new("disco_");
+                    g_string_append(id, from);
+                    xmpp_stanza_t *iq = stanza_create_disco_iq(jabber_conn.ctx, id->str, from, node);
+                    xmpp_send(jabber_conn.conn, iq);
+                    xmpp_stanza_release(iq);
+                    g_string_free(id, TRUE);
+                }
+            }
+
+            return caps_key;
+        }
+    }
+    return NULL;
+}
+
 static int
 _presence_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata)
@@ -1055,6 +1295,8 @@ _presence_handler(xmpp_conn_t * const conn,
             g_date_time_unref(now);
         }
 
+        char *caps_key = _handle_presence_caps(stanza);
+
         xmpp_stanza_t *status = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_STATUS);
         if (status != NULL)
             status_str = xmpp_stanza_get_text(status);
@@ -1069,7 +1311,7 @@ _presence_handler(xmpp_conn_t * const conn,
                 show_str = "online";
 
             if (strcmp(my_jid->barejid, from_jid->barejid) !=0) {
-                prof_handle_contact_online(from_jid->barejid, show_str, status_str, last_activity);
+                prof_handle_contact_online(from_jid->barejid, show_str, status_str, last_activity, caps_key);
             }
         } else if (strcmp(type, STANZA_TYPE_UNAVAILABLE) == 0) {
             if (strcmp(my_jid->barejid, from_jid->barejid) !=0) {