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/connection.c23
-rw-r--r--src/xmpp/connection.h2
-rw-r--r--src/xmpp/iq.c26
-rw-r--r--src/xmpp/iq.h6
-rw-r--r--src/xmpp/message.c259
-rw-r--r--src/xmpp/message.h4
-rw-r--r--src/xmpp/omemo.c448
-rw-r--r--src/xmpp/omemo.h11
-rw-r--r--src/xmpp/roster.c4
-rw-r--r--src/xmpp/session.c21
-rw-r--r--src/xmpp/stanza.c340
-rw-r--r--src/xmpp/stanza.h22
-rw-r--r--src/xmpp/xmpp.h8
13 files changed, 1124 insertions, 50 deletions
diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c
index 2adda46e..afcd8199 100644
--- a/src/xmpp/connection.c
+++ b/src/xmpp/connection.c
@@ -63,6 +63,7 @@ typedef struct prof_conn_t {
     char *domain;
     GHashTable *available_resources;
     GHashTable *features_by_jid;
+    GHashTable *requested_features;
 } ProfConnection;
 
 static ProfConnection conn;
@@ -89,6 +90,7 @@ connection_init(void)
     conn.domain = NULL;
     conn.features_by_jid = NULL;
     conn.available_resources = g_hash_table_new_full(g_str_hash, g_str_equal, free, (GDestroyNotify)resource_destroy);
+    conn.requested_features = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);
 }
 
 void
@@ -231,6 +233,10 @@ connection_clear_data(void)
     if (conn.available_resources) {
         g_hash_table_remove_all(conn.available_resources);
     }
+
+    if (conn.requested_features) {
+        g_hash_table_remove_all(conn.requested_features);
+    }
 }
 
 #ifdef HAVE_LIBMESODE
@@ -314,11 +320,20 @@ connection_jid_for_feature(const char *const feature)
 }
 
 void
+connection_request_features(void)
+{
+    /* We don't record it as a requested feature to avoid triggering th
+     * sv_ev_connection_features_received too soon */
+    iq_disco_info_request_onconnect(conn.domain);
+}
+
+void
 connection_set_disco_items(GSList *items)
 {
     GSList *curr = items;
     while (curr) {
         DiscoItem *item = curr->data;
+        g_hash_table_insert(conn.requested_features, strdup(item->jid), NULL);
         g_hash_table_insert(conn.features_by_jid, strdup(item->jid),
             g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL));
 
@@ -357,6 +372,14 @@ connection_get_fulljid(void)
     }
 }
 
+void
+connection_features_received(const char *const jid)
+{
+    if (g_hash_table_remove(conn.requested_features, jid) && g_hash_table_size(conn.requested_features) == 0) {
+        sv_ev_connection_features_received();
+    }
+}
+
 GHashTable*
 connection_get_features(const char *const jid)
 {
diff --git a/src/xmpp/connection.h b/src/xmpp/connection.h
index 170bc2bf..044cf368 100644
--- a/src/xmpp/connection.h
+++ b/src/xmpp/connection.h
@@ -53,6 +53,8 @@ void connection_set_disco_items(GSList *items);
 xmpp_conn_t* connection_get_conn(void);
 xmpp_ctx_t* connection_get_ctx(void);
 char *connection_get_domain(void);
+void connection_request_features(void);
+void connection_features_received(const char *const jid);
 GHashTable* connection_get_features(const char *const jid);
 
 void connection_clear_data(void);
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index a77ef59b..d6e4c153 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -77,11 +77,11 @@ typedef struct p_room_info_data_t {
     gboolean display;
 } ProfRoomInfoData;
 
-typedef struct p_id_handle_t {
-    ProfIdCallback func;
-    ProfIdFreeCallback free_func;
+typedef struct p_iq_handle_t {
+    ProfIqCallback func;
+    ProfIqFreeCallback free_func;
     void *userdata;
-} ProfIdHandler;
+} ProfIqHandler;
 
 typedef struct privilege_set_t {
     char *item;
@@ -205,7 +205,7 @@ _iq_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const us
 
     const char *id = xmpp_stanza_get_id(stanza);
     if (id) {
-        ProfIdHandler *handler = g_hash_table_lookup(id_handlers, id);
+        ProfIqHandler *handler = g_hash_table_lookup(id_handlers, id);
         if (handler) {
             int keep = handler->func(stanza, handler->userdata);
             if (!keep) {
@@ -234,7 +234,7 @@ iq_handlers_init(void)
         GList *keys = g_hash_table_get_keys(id_handlers);
         GList *curr = keys;
         while (curr) {
-            ProfIdHandler *handler = g_hash_table_lookup(id_handlers, curr->data);
+            ProfIqHandler *handler = g_hash_table_lookup(id_handlers, curr->data);
             if (handler->free_func && handler->userdata) {
                 handler->free_func(handler->userdata);
             }
@@ -248,9 +248,9 @@ iq_handlers_init(void)
 }
 
 void
-iq_id_handler_add(const char *const id, ProfIdCallback func, ProfIdFreeCallback free_func, void *userdata)
+iq_id_handler_add(const char *const id, ProfIqCallback func, ProfIqFreeCallback free_func, void *userdata)
 {
-    ProfIdHandler *handler = malloc(sizeof(ProfIdHandler));
+    ProfIqHandler *handler = malloc(sizeof(ProfIqHandler));
     handler->func = func;
     handler->free_func = free_func;
     handler->userdata = userdata;
@@ -438,7 +438,7 @@ iq_room_info_request(const char *const room, gboolean display_result)
     cb_data->room = strdup(room);
     cb_data->display = display_result;
 
-    iq_id_handler_add(id, _room_info_response_id_handler, (ProfIdFreeCallback)_iq_free_room_data, cb_data);
+    iq_id_handler_add(id, _room_info_response_id_handler, (ProfIqFreeCallback)_iq_free_room_data, cb_data);
 
     free(id);
 
@@ -651,7 +651,7 @@ iq_room_affiliation_set(const char *const room, const char *const jid, char *aff
     affiliation_set->item = strdup(jid);
     affiliation_set->privilege = strdup(affiliation);
 
-    iq_id_handler_add(id, _room_affiliation_set_result_id_handler, (ProfIdFreeCallback)_iq_free_affiliation_set, affiliation_set);
+    iq_id_handler_add(id, _room_affiliation_set_result_id_handler, (ProfIqFreeCallback)_iq_free_affiliation_set, affiliation_set);
 
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
@@ -670,7 +670,7 @@ iq_room_role_set(const char *const room, const char *const nick, char *role,
     role_set->item = strdup(nick);
     role_set->privilege = strdup(role);
 
-    iq_id_handler_add(id, _room_role_set_result_id_handler, (ProfIdFreeCallback)_iq_free_affiliation_set, role_set);
+    iq_id_handler_add(id, _room_role_set_result_id_handler, (ProfIqFreeCallback)_iq_free_affiliation_set, role_set);
 
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
@@ -697,7 +697,7 @@ iq_send_ping(const char *const target)
     const char *id = xmpp_stanza_get_id(iq);
 
     GDateTime *now = g_date_time_new_now_local();
-    iq_id_handler_add(id, _manual_pong_id_handler, (ProfIdFreeCallback)g_date_time_unref, now);
+    iq_id_handler_add(id, _manual_pong_id_handler, (ProfIqFreeCallback)g_date_time_unref, now);
 
     iq_send_stanza(iq);
     xmpp_stanza_release(iq);
@@ -2291,6 +2291,8 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t *const stanza, void *con
         }
     }
 
+    connection_features_received(from);
+
     return 0;
 }
 
diff --git a/src/xmpp/iq.h b/src/xmpp/iq.h
index 025d5e9f..bc273db4 100644
--- a/src/xmpp/iq.h
+++ b/src/xmpp/iq.h
@@ -35,12 +35,12 @@
 #ifndef XMPP_IQ_H
 #define XMPP_IQ_H
 
-typedef int(*ProfIdCallback)(xmpp_stanza_t *const stanza, void *const userdata);
-typedef void(*ProfIdFreeCallback)(void *userdata);
+typedef int(*ProfIqCallback)(xmpp_stanza_t *const stanza, void *const userdata);
+typedef void(*ProfIqFreeCallback)(void *userdata);
 
 void iq_handlers_init(void);
 void iq_send_stanza(xmpp_stanza_t *const stanza);
-void iq_id_handler_add(const char *const id, ProfIdCallback func, ProfIdFreeCallback free_func, void *userdata);
+void iq_id_handler_add(const char *const id, ProfIqCallback func, ProfIqFreeCallback free_func, void *userdata);
 void iq_disco_info_request_onconnect(gchar *jid);
 void iq_disco_items_request_onconnect(gchar *jid);
 void iq_send_caps_request(const char *const to, const char *const id, const char *const node, const char *const ver);
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index adea5c10..47cf35d7 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -52,6 +52,7 @@
 #include "pgp/gpg.h"
 #include "plugins/plugins.h"
 #include "ui/ui.h"
+#include "ui/window_list.h"
 #include "xmpp/chat_session.h"
 #include "xmpp/muc.h"
 #include "xmpp/session.h"
@@ -62,6 +63,17 @@
 #include "xmpp/connection.h"
 #include "xmpp/xmpp.h"
 
+#ifdef HAVE_OMEMO
+#include "xmpp/omemo.h"
+#include "omemo/omemo.h"
+#endif
+
+typedef struct p_message_handle_t {
+    ProfMessageCallback func;
+    ProfMessageFreeCallback free_func;
+    void *userdata;
+} ProfMessageHandler;
+
 static int _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata);
 
 static void _handle_error(xmpp_stanza_t *const stanza);
@@ -74,6 +86,8 @@ static void _handle_chat(xmpp_stanza_t *const stanza);
 
 static void _send_message_stanza(xmpp_stanza_t *const stanza);
 
+static GHashTable *pubsub_event_handlers;
+
 static int
 _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *const userdata)
 {
@@ -118,6 +132,23 @@ _message_handler(xmpp_conn_t *const conn, xmpp_stanza_t *const stanza, void *con
         _handle_receipt_received(stanza);
     }
 
+    xmpp_stanza_t *event = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB_EVENT);
+    if (event) {
+        xmpp_stanza_t *child = xmpp_stanza_get_children(event);
+        if (child) {
+            const char *node = xmpp_stanza_get_attribute(child, STANZA_ATTR_NODE);
+            if (node) {
+                ProfMessageHandler *handler = g_hash_table_lookup(pubsub_event_handlers, node);
+                if (handler) {
+                    int keep = handler->func(stanza, handler->userdata);
+                    if (!keep) {
+                        g_hash_table_remove(pubsub_event_handlers, node);
+                    }
+                }
+            }
+        }
+    }
+
     _handle_chat(stanza);
 
     return 1;
@@ -129,6 +160,33 @@ message_handlers_init(void)
     xmpp_conn_t * const conn = connection_get_conn();
     xmpp_ctx_t * const ctx = connection_get_ctx();
     xmpp_handler_add(conn, _message_handler, NULL, STANZA_NAME_MESSAGE, NULL, ctx);
+
+    if (pubsub_event_handlers) {
+        GList *keys = g_hash_table_get_keys(pubsub_event_handlers);
+        GList *curr = keys;
+        while (curr) {
+            ProfMessageHandler *handler = g_hash_table_lookup(pubsub_event_handlers, curr->data);
+            if (handler->free_func && handler->userdata) {
+                handler->free_func(handler->userdata);
+            }
+            curr = g_list_next(curr);
+        }
+        g_list_free(keys);
+        g_hash_table_destroy(pubsub_event_handlers);
+    }
+
+    pubsub_event_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+}
+
+void
+message_pubsub_event_handler_add(const char *const node, ProfMessageCallback func, ProfMessageFreeCallback free_func, void *userdata)
+{
+    ProfMessageHandler *handler = malloc(sizeof(ProfMessageHandler));
+    handler->func = func;
+    handler->free_func = free_func;
+    handler->userdata = userdata;
+
+    g_hash_table_insert(pubsub_event_handlers, strdup(node), handler);
 }
 
 char*
@@ -254,6 +312,118 @@ message_send_chat_otr(const char *const barejid, const char *const msg, gboolean
     return id;
 }
 
+#ifdef HAVE_OMEMO
+char*
+message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys,
+    const unsigned char *const iv, size_t iv_len,
+    const unsigned char *const ciphertext, size_t ciphertext_len,
+    gboolean request_receipt, gboolean muc)
+{
+    char *state = chat_session_get_state(jid);
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    char *id;
+    xmpp_stanza_t *message;
+    if (muc) {
+        id = connection_create_stanza_id("muc");
+        message = xmpp_message_new(ctx, STANZA_TYPE_GROUPCHAT, jid, id);
+        stanza_attach_origin_id(ctx, message, id);
+    } else {
+        id = connection_create_stanza_id("msg");
+        message = xmpp_message_new(ctx, STANZA_TYPE_CHAT, jid, id);
+    }
+
+    xmpp_stanza_t *encrypted = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(encrypted, "encrypted");
+    xmpp_stanza_set_ns(encrypted, STANZA_NS_OMEMO);
+
+    xmpp_stanza_t *header = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(header, "header");
+    char *sid_text = g_strdup_printf("%d", sid);
+    xmpp_stanza_set_attribute(header, "sid", sid_text);
+    g_free(sid_text);
+
+    GList *key_iter;
+    for (key_iter = keys; key_iter != NULL; key_iter = key_iter->next) {
+        omemo_key_t *key = (omemo_key_t *)key_iter->data;
+
+        xmpp_stanza_t *key_stanza = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(key_stanza, "key");
+        char *rid = g_strdup_printf("%d", key->device_id);
+        xmpp_stanza_set_attribute(key_stanza, "rid", rid);
+        g_free(rid);
+        if (key->prekey) {
+            xmpp_stanza_set_attribute(key_stanza, "prekey", "true");
+        }
+
+        gchar *key_raw = g_base64_encode(key->data, key->length);
+        xmpp_stanza_t *key_text = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_text(key_text, key_raw);
+        g_free(key_raw);
+
+        xmpp_stanza_add_child(key_stanza, key_text);
+        xmpp_stanza_add_child(header, key_stanza);
+        xmpp_stanza_release(key_text);
+        xmpp_stanza_release(key_stanza);
+    }
+
+    xmpp_stanza_t *iv_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iv_stanza, "iv");
+
+    gchar *iv_raw = g_base64_encode(iv, iv_len);
+    xmpp_stanza_t *iv_text = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_text(iv_text, iv_raw);
+    g_free(iv_raw);
+
+    xmpp_stanza_add_child(iv_stanza, iv_text);
+    xmpp_stanza_add_child(header, iv_stanza);
+    xmpp_stanza_release(iv_text);
+    xmpp_stanza_release(iv_stanza);
+
+    xmpp_stanza_add_child(encrypted, header);
+    xmpp_stanza_release(header);
+
+    xmpp_stanza_t *payload = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(payload, "payload");
+
+    gchar *ciphertext_raw = g_base64_encode(ciphertext, ciphertext_len);
+    xmpp_stanza_t *payload_text = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_text(payload_text, ciphertext_raw);
+    g_free(ciphertext_raw);
+
+    xmpp_stanza_add_child(payload, payload_text);
+    xmpp_stanza_add_child(encrypted, payload);
+    xmpp_stanza_release(payload_text);
+    xmpp_stanza_release(payload);
+
+    xmpp_stanza_add_child(message, encrypted);
+    xmpp_stanza_release(encrypted);
+
+    xmpp_stanza_t *body = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(body, "body");
+    xmpp_stanza_t *body_text = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_text(body_text, "You received a message encrypted with OMEMO but your client doesn't support OMEMO.");
+    xmpp_stanza_add_child(body, body_text);
+    xmpp_stanza_release(body_text);
+    xmpp_stanza_add_child(message, body);
+    xmpp_stanza_release(body);
+
+    if (state) {
+        stanza_attach_state(ctx, message, state);
+    }
+
+    stanza_attach_hints_store(ctx, message);
+
+    if (request_receipt) {
+        stanza_attach_receipt_request(ctx, message);
+    }
+
+    _send_message_stanza(message);
+    xmpp_stanza_release(message);
+
+    return id;
+}
+#endif
+
 void
 message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url)
 {
@@ -273,23 +443,24 @@ message_send_private(const char *const fulljid, const char *const msg, const cha
     xmpp_stanza_release(message);
 }
 
-void
+char*
 message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url)
 {
     xmpp_ctx_t * const ctx = connection_get_ctx();
     char *id = connection_create_stanza_id("muc");
 
     xmpp_stanza_t *message = xmpp_message_new(ctx, STANZA_TYPE_GROUPCHAT, roomjid, id);
+    stanza_attach_origin_id(ctx, message, id);
     xmpp_message_set_body(message, msg);
 
-    free(id);
-
     if (oob_url) {
         stanza_attach_x_oob_url(ctx, message, oob_url);
     }
 
     _send_message_stanza(message);
     xmpp_stanza_release(message);
+
+    return id;
 }
 
 void
@@ -518,6 +689,14 @@ _handle_groupchat(xmpp_stanza_t *const stanza)
 {
     xmpp_ctx_t *ctx = connection_get_ctx();
     char *message = NULL;
+
+    const char *id = xmpp_stanza_get_id(stanza);
+
+    xmpp_stanza_t *origin = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_STABLE_ID);
+    if (origin && g_strcmp0(xmpp_stanza_get_name(origin), STANZA_NAME_ORIGIN_ID) == 0) {
+        id = xmpp_stanza_get_attribute(origin, STANZA_ATTR_ID);
+    }
+
     const char *room_jid = xmpp_stanza_get_from(stanza);
     Jid *jid = jid_create(room_jid);
 
@@ -560,19 +739,28 @@ _handle_groupchat(xmpp_stanza_t *const stanza)
         return;
     }
 
-    message = xmpp_message_get_body(stanza);
+    // check omemo encryption
+    gboolean omemo = FALSE;
+#ifdef HAVE_OMEMO
+    message = omemo_receive_message(stanza);
+    omemo = message != NULL;
+#endif
+
     if (!message) {
-        jid_destroy(jid);
-        return;
+        message = xmpp_message_get_body(stanza);
+        if (!message) {
+            jid_destroy(jid);
+            return;
+        }
     }
 
     // determine if the notifications happened whilst offline
     GDateTime *timestamp = stanza_get_delay(stanza);
     if (timestamp) {
-        sv_ev_room_history(jid->barejid, jid->resourcepart, timestamp, message);
+        sv_ev_room_history(jid->barejid, jid->resourcepart, timestamp, message, omemo);
         g_date_time_unref(timestamp);
     } else {
-        sv_ev_room_message(jid->barejid, jid->resourcepart, message);
+        sv_ev_room_message(jid->barejid, jid->resourcepart, message, id, omemo);
     }
 
     xmpp_free(ctx, message);
@@ -675,6 +863,7 @@ _private_chat_handler(xmpp_stanza_t *const stanza, const char *const fulljid)
 static gboolean
 _handle_carbons(xmpp_stanza_t *const stanza)
 {
+    char *message_txt = NULL;
     xmpp_stanza_t *carbons = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CARBONS);
     if (!carbons) {
         return FALSE;
@@ -708,10 +897,19 @@ _handle_carbons(xmpp_stanza_t *const stanza)
         return TRUE;
     }
 
-    char *message_txt = xmpp_message_get_body(message);
+    // check omemo encryption
+    gboolean omemo = FALSE;
+#ifdef HAVE_OMEMO
+    message_txt = omemo_receive_message(message);
+    omemo = message_txt != NULL;
+#endif
+
     if (!message_txt) {
-        log_warning("Carbon received with no message.");
-        return TRUE;
+        message_txt = xmpp_message_get_body(message);
+        if (!message_txt) {
+            log_warning("Carbon received with no message.");
+            return TRUE;
+        }
     }
 
     Jid *my_jid = jid_create(connection_get_fulljid());
@@ -739,11 +937,11 @@ _handle_carbons(xmpp_stanza_t *const stanza)
 
     // if we are the recipient, treat as standard incoming message
     if (g_strcmp0(my_jid->barejid, jid_to->barejid) == 0) {
-        sv_ev_incoming_carbon(jid_from->barejid, jid_from->resourcepart, message_txt, enc_message);
+        sv_ev_incoming_carbon(jid_from->barejid, jid_from->resourcepart, message_txt, enc_message, omemo);
 
     // else treat as a sent message
     } else {
-        sv_ev_outgoing_carbon(jid_to->barejid, message_txt, enc_message);
+        sv_ev_outgoing_carbon(jid_to->barejid, message_txt, enc_message, omemo);
     }
 
     xmpp_ctx_t *ctx = connection_get_ctx();
@@ -760,6 +958,7 @@ _handle_carbons(xmpp_stanza_t *const stanza)
 static void
 _handle_chat(xmpp_stanza_t *const stanza)
 {
+    char *message = NULL;
     // ignore if type not chat or absent
     const char *type = xmpp_stanza_get_type(stanza);
     if (!(g_strcmp0(type, "chat") == 0 || type == NULL)) {
@@ -772,6 +971,13 @@ _handle_chat(xmpp_stanza_t *const stanza)
         return;
     }
 
+    // check omemo encryption
+    gboolean omemo = FALSE;
+#ifdef HAVE_OMEMO
+    message = omemo_receive_message(stanza);
+    omemo = message != NULL;
+#endif
+
     // ignore handled namespaces
     xmpp_stanza_t *conf = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CONFERENCE);
     xmpp_stanza_t *captcha = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_CAPTCHA);
@@ -801,19 +1007,24 @@ _handle_chat(xmpp_stanza_t *const stanza)
     // standard chat message, use jid without resource
     xmpp_ctx_t *ctx = connection_get_ctx();
     GDateTime *timestamp = stanza_get_delay(stanza);
-    if (body) {
-        char *message = xmpp_stanza_get_text(body);
-        if (message) {
-            char *enc_message = NULL;
-            xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_ENCRYPTED);
-            if (x) {
-                enc_message = xmpp_stanza_get_text(x);
-            }
-            sv_ev_incoming_message(jid->barejid, jid->resourcepart, message, enc_message, timestamp);
-            xmpp_free(ctx, enc_message);
+    if (!message && body) {
+        message = xmpp_stanza_get_text(body);
+    }
 
-            _receipt_request_handler(stanza);
+    if (message) {
+        char *enc_message = NULL;
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_ENCRYPTED);
+        if (x) {
+            enc_message = xmpp_stanza_get_text(x);
+        }
+        sv_ev_incoming_message(jid->barejid, jid->resourcepart, message, enc_message, timestamp, omemo);
+        xmpp_free(ctx, enc_message);
 
+        _receipt_request_handler(stanza);
+
+        if (omemo) {
+            free(message);
+        } else {
             xmpp_free(ctx, message);
         }
     }
diff --git a/src/xmpp/message.h b/src/xmpp/message.h
index dee9be2d..0c81ca39 100644
--- a/src/xmpp/message.h
+++ b/src/xmpp/message.h
@@ -35,6 +35,10 @@
 #ifndef XMPP_MESSAGE_H
 #define XMPP_MESSAGE_H
 
+typedef int(*ProfMessageCallback)(xmpp_stanza_t *const stanza, void *const userdata);
+typedef void(*ProfMessageFreeCallback)(void *userdata);
+
 void message_handlers_init(void);
+void message_pubsub_event_handler_add(const char *const node, ProfMessageCallback func, ProfMessageFreeCallback free_func, void *userdata);
 
 #endif
diff --git a/src/xmpp/omemo.c b/src/xmpp/omemo.c
new file mode 100644
index 00000000..4b77ef23
--- /dev/null
+++ b/src/xmpp/omemo.c
@@ -0,0 +1,448 @@
+#include <glib.h>
+
+#include "log.h"
+#include "xmpp/connection.h"
+#include "xmpp/form.h"
+#include "xmpp/iq.h"
+#include "xmpp/message.h"
+#include "xmpp/stanza.h"
+
+#include "omemo/omemo.h"
+
+static int _omemo_receive_devicelist(xmpp_stanza_t *const stanza, void *const userdata);
+static int _omemo_bundle_publish_result(xmpp_stanza_t *const stanza, void *const userdata);
+static int _omemo_bundle_publish_configure(xmpp_stanza_t *const stanza, void *const userdata);
+static int _omemo_bundle_publish_configure_result(xmpp_stanza_t *const stanza, void *const userdata);
+
+void
+omemo_devicelist_subscribe(void)
+{
+    message_pubsub_event_handler_add(STANZA_NS_OMEMO_DEVICELIST, _omemo_receive_devicelist, NULL, NULL);
+
+    caps_add_feature(XMPP_FEATURE_OMEMO_DEVICELIST_NOTIFY);
+}
+
+void
+omemo_devicelist_publish(GList *device_list)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_omemo_devicelist_publish(ctx, device_list);
+
+    if (connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) {
+        stanza_attach_publish_options(ctx, iq, "pubsub#access_model", "open");
+    }
+
+    iq_send_stanza(iq);
+    xmpp_stanza_release(iq);
+}
+
+void
+omemo_devicelist_request(const char * const jid)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    char *id = connection_create_stanza_id("devicelist_request");
+
+    xmpp_stanza_t *iq = stanza_create_omemo_devicelist_request(ctx, id, jid);
+    iq_id_handler_add(id, _omemo_receive_devicelist, NULL, NULL);
+
+    iq_send_stanza(iq);
+
+    free(id);
+    xmpp_stanza_release(iq);
+}
+
+void
+omemo_bundle_publish(gboolean first)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    unsigned char *identity_key = NULL;
+    size_t identity_key_length;
+    unsigned char *signed_prekey = NULL;
+    size_t signed_prekey_length;
+    unsigned char *signed_prekey_signature = NULL;
+    size_t signed_prekey_signature_length;
+    GList *prekeys = NULL, *ids = NULL, *lengths = NULL;
+
+    omemo_identity_key(&identity_key, &identity_key_length);
+    omemo_signed_prekey(&signed_prekey, &signed_prekey_length);
+    omemo_signed_prekey_signature(&signed_prekey_signature, &signed_prekey_signature_length);
+    omemo_prekeys(&prekeys, &ids, &lengths);
+
+    char *id = connection_create_stanza_id("omemo_bundle_publish");
+    xmpp_stanza_t *iq = stanza_create_omemo_bundle_publish(ctx, id,
+        omemo_device_id(), identity_key, identity_key_length, signed_prekey,
+        signed_prekey_length, signed_prekey_signature,
+        signed_prekey_signature_length, prekeys, ids, lengths);
+
+    g_list_free_full(prekeys, free);
+    g_list_free(lengths);
+    g_list_free(ids);
+
+    if (connection_supports(XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS)) {
+        stanza_attach_publish_options(ctx, iq, "pubsub#access_model", "open");
+    }
+
+    iq_id_handler_add(id, _omemo_bundle_publish_result, NULL, GINT_TO_POINTER(first));
+
+    iq_send_stanza(iq);
+
+    xmpp_stanza_release(iq);
+    free(identity_key);
+    free(signed_prekey);
+    free(signed_prekey_signature);
+    free(id);
+}
+
+void
+omemo_bundle_request(const char * const jid, uint32_t device_id, ProfIqCallback func, ProfIqFreeCallback free_func, void *userdata)
+{
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    char *id = connection_create_stanza_id("bundle_request");
+
+    xmpp_stanza_t *iq = stanza_create_omemo_bundle_request(ctx, id, jid, device_id);
+    iq_id_handler_add(id, func, free_func, userdata);
+
+    iq_send_stanza(iq);
+
+    free(id);
+    xmpp_stanza_release(iq);
+}
+
+int
+omemo_start_device_session_handle_bundle(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    char *from = NULL;
+    const char *from_attr = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (!from_attr) {
+        Jid *jid = jid_create(connection_get_fulljid());
+        from = strdup(jid->barejid);
+        jid_destroy(jid);
+    } else {
+        from = strdup(from_attr);
+    }
+
+    if (g_strcmp0(from, userdata) != 0) {
+        return 1;
+    }
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB);
+    if (!pubsub) {
+        return 1;
+    }
+
+    xmpp_stanza_t *items = xmpp_stanza_get_child_by_name(pubsub, "items");
+    if (!items) {
+        return 1;
+    }
+    const char *node = xmpp_stanza_get_attribute(items, "node");
+    char *device_id_str = strstr(node, ":");
+    if (!device_id_str) {
+        return 1;
+    }
+
+    uint32_t device_id = strtoul(++device_id_str, NULL, 10);
+
+    xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(items, "item");
+    if (!item) {
+        return 1;
+    }
+
+    xmpp_stanza_t *bundle = xmpp_stanza_get_child_by_ns(item, STANZA_NS_OMEMO);
+    if (!bundle) {
+        return 1;
+    }
+
+    xmpp_stanza_t *prekeys = xmpp_stanza_get_child_by_name(bundle, "prekeys");
+    if (!prekeys) {
+        return 1;
+    }
+
+    GList *prekeys_list = NULL;
+    xmpp_stanza_t *prekey;
+    for (prekey = xmpp_stanza_get_children(prekeys); prekey != NULL; prekey = xmpp_stanza_get_next(prekey)) {
+        omemo_key_t *key = malloc(sizeof(omemo_key_t));
+
+        const char *prekey_id_text = xmpp_stanza_get_attribute(prekey, "preKeyId");
+        if (!prekey_id_text) {
+            return 1;
+        }
+        key->id = strtoul(prekey_id_text, NULL, 10);
+        xmpp_stanza_t *prekey_text = xmpp_stanza_get_children(prekey);
+        if (!prekey_text) {
+            return 1;
+        }
+        char *prekey_b64 = xmpp_stanza_get_text(prekey_text);
+        key->data = g_base64_decode(prekey_b64, &key->length);
+        free(prekey_b64);
+        key->prekey = TRUE;
+        key->device_id = device_id;
+
+        prekeys_list = g_list_append(prekeys_list, key);
+    }
+
+    xmpp_stanza_t *signed_prekey = xmpp_stanza_get_child_by_name(bundle, "signedPreKeyPublic");
+    if (!signed_prekey) {
+        return 1;
+    }
+    const char *signed_prekey_id_text = xmpp_stanza_get_attribute(signed_prekey, "signedPreKeyId");
+    if (!signed_prekey_id_text) {
+        return 1;
+    }
+    uint32_t signed_prekey_id = strtoul(signed_prekey_id_text, NULL, 10);
+    xmpp_stanza_t *signed_prekey_text = xmpp_stanza_get_children(signed_prekey);
+    if (!signed_prekey_text) {
+        return 1;
+    }
+    size_t signed_prekey_len;
+    char *signed_prekey_b64 = xmpp_stanza_get_text(signed_prekey_text);
+    unsigned char *signed_prekey_raw = g_base64_decode(signed_prekey_b64, &signed_prekey_len);
+    free(signed_prekey_b64);
+
+    xmpp_stanza_t *signed_prekey_signature = xmpp_stanza_get_child_by_name(bundle, "signedPreKeySignature");
+    if (!signed_prekey_signature) {
+        return 1;
+    }
+    xmpp_stanza_t *signed_prekey_signature_text = xmpp_stanza_get_children(signed_prekey_signature);
+    if (!signed_prekey_signature_text) {
+        return 1;
+    }
+    size_t signed_prekey_signature_len;
+    char *signed_prekey_signature_b64 = xmpp_stanza_get_text(signed_prekey_signature_text);
+    unsigned char *signed_prekey_signature_raw = g_base64_decode(signed_prekey_signature_b64, &signed_prekey_signature_len);
+    free(signed_prekey_signature_b64);
+
+    xmpp_stanza_t *identity_key = xmpp_stanza_get_child_by_name(bundle, "identityKey");
+    if (!identity_key) {
+        return 1;
+    }
+    xmpp_stanza_t *identity_key_text = xmpp_stanza_get_children(identity_key);
+    if (!identity_key_text) {
+        return 1;
+    }
+    size_t identity_key_len;
+    char *identity_key_b64 = xmpp_stanza_get_text(identity_key_text);
+    unsigned char *identity_key_raw = g_base64_decode(identity_key_b64, &identity_key_len);
+    free(identity_key_b64);
+
+    omemo_start_device_session(from, device_id, prekeys_list, signed_prekey_id,
+        signed_prekey_raw, signed_prekey_len, signed_prekey_signature_raw,
+        signed_prekey_signature_len, identity_key_raw, identity_key_len);
+
+    free(from);
+    g_list_free_full(prekeys_list, (GDestroyNotify)omemo_key_free);
+    g_free(signed_prekey_raw);
+    g_free(identity_key_raw);
+    g_free(signed_prekey_signature_raw);
+    return 1;
+}
+
+char *
+omemo_receive_message(xmpp_stanza_t *const stanza)
+{
+    const char *type = xmpp_stanza_get_type(stanza);
+
+    xmpp_stanza_t *encrypted = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_OMEMO);
+    if (!encrypted) {
+        return NULL;
+    }
+
+    xmpp_stanza_t *header = xmpp_stanza_get_child_by_name(encrypted, "header");
+    if (!header) {
+        return NULL;
+    }
+
+    const char *sid_text = xmpp_stanza_get_attribute(header, "sid");
+    if (!sid_text) {
+        return NULL;
+    }
+    uint32_t sid = strtoul(sid_text, NULL, 10);
+
+    xmpp_stanza_t *iv = xmpp_stanza_get_child_by_name(header, "iv");
+    if (!iv) {
+        return NULL;
+    }
+    char *iv_text = xmpp_stanza_get_text(iv);
+    if (!iv_text) {
+        return NULL;
+    }
+    size_t iv_len;
+    unsigned char *iv_raw = g_base64_decode(iv_text, &iv_len);
+
+    xmpp_stanza_t *payload = xmpp_stanza_get_child_by_name(encrypted, "payload");
+    if (!payload) {
+        return NULL;
+    }
+    char *payload_text = xmpp_stanza_get_text(payload);
+    if (!payload_text) {
+        return NULL;
+    }
+    size_t payload_len;
+    unsigned char *payload_raw = g_base64_decode(payload_text, &payload_len);
+
+    GList *keys = NULL;
+    xmpp_stanza_t *key_stanza;
+    for (key_stanza = xmpp_stanza_get_children(header); key_stanza != NULL; key_stanza = xmpp_stanza_get_next(key_stanza)) {
+        if (g_strcmp0(xmpp_stanza_get_name(key_stanza), "key") != 0) {
+            continue;
+        }
+
+        omemo_key_t *key = malloc(sizeof(omemo_key_t));
+        char *key_text = xmpp_stanza_get_text(key_stanza);
+        if (!key_text) {
+            goto skip;
+        }
+
+
+        const char *rid_text = xmpp_stanza_get_attribute(key_stanza, "rid");
+        key->device_id = strtoul(rid_text, NULL, 10);
+        if (!key->device_id) {
+            goto skip;
+        }
+        key->data = g_base64_decode(key_text, &key->length);
+        free(key_text);
+        key->prekey = g_strcmp0(xmpp_stanza_get_attribute(key_stanza, "prekey"), "true") == 0;
+        keys = g_list_append(keys, key);
+        continue;
+
+skip:
+        free(key);
+    }
+
+    const char *from = xmpp_stanza_get_from(stanza);
+
+    char *plaintext = omemo_on_message_recv(from, sid, iv_raw, iv_len,
+        keys, payload_raw, payload_len,
+        g_strcmp0(type, STANZA_TYPE_GROUPCHAT) == 0);
+
+    g_list_free_full(keys, (GDestroyNotify)omemo_key_free);
+    g_free(iv_raw);
+    g_free(payload_raw);
+    g_free(iv_text);
+    g_free(payload_text);
+
+    return plaintext;
+}
+
+static int
+_omemo_receive_devicelist(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    GList *device_list = NULL;
+    const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+
+    xmpp_stanza_t *root = NULL;
+    xmpp_stanza_t *event = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB_EVENT);
+    if (event) {
+        root = event;
+    }
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_get_child_by_ns(stanza, STANZA_NS_PUBSUB);
+    if (pubsub) {
+        root = pubsub;
+    }
+
+    if (!root) {
+        return 1;
+    }
+
+    xmpp_stanza_t *items = xmpp_stanza_get_child_by_name(root, "items");
+    if (!items) {
+        return 1;
+    }
+
+    xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(items, "item");
+    if (item) {
+        xmpp_stanza_t *list = xmpp_stanza_get_child_by_ns(item, STANZA_NS_OMEMO);
+        if (!list) {
+            return 1;
+        }
+
+        xmpp_stanza_t *device;
+        for (device = xmpp_stanza_get_children(list); device != NULL; device = xmpp_stanza_get_next(device)) {
+            const char *id = xmpp_stanza_get_id(device);
+            device_list = g_list_append(device_list, GINT_TO_POINTER(strtoul(id, NULL, 10)));
+        }
+    }
+    omemo_set_device_list(from, device_list);
+
+    return 1;
+}
+
+static int
+_omemo_bundle_publish_result(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    const char *type = xmpp_stanza_get_type(stanza);
+
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) != 0) {
+        return 0;
+    }
+
+    if (!GPOINTER_TO_INT(userdata)) {
+        log_error("OMEMO: definitely cannot publish bundle with an open access model");
+        return 0;
+    }
+
+    log_info("OMEMO: cannot publish bundle with open access model, trying to configure node");
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    Jid *jid = jid_create(connection_get_fulljid());
+    char *id = connection_create_stanza_id("omemo_bundle_node_configure_request");
+    char *node = g_strdup_printf("%s:%d", STANZA_NS_OMEMO_BUNDLES, omemo_device_id());
+    xmpp_stanza_t *iq = stanza_create_pubsub_configure_request(ctx, id, jid->barejid, node);
+    g_free(node);
+
+    iq_id_handler_add(id, _omemo_bundle_publish_configure, NULL, userdata);
+
+    iq_send_stanza(iq);
+
+    xmpp_stanza_release(iq);
+    free(id);
+    jid_destroy(jid);
+    return 0;
+}
+
+static int
+_omemo_bundle_publish_configure(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    /* TODO handle error */
+    xmpp_stanza_t *pubsub = xmpp_stanza_get_child_by_name(stanza, "pubsub");
+    xmpp_stanza_t *configure = xmpp_stanza_get_child_by_name(pubsub, STANZA_NAME_CONFIGURE);
+    xmpp_stanza_t *x = xmpp_stanza_get_child_by_name(configure, "x");
+
+    DataForm* form = form_create(x);
+    char *tag = g_hash_table_lookup(form->var_to_tag, "pubsub#access_model");
+    if (!tag) {
+        log_info("OMEMO: cannot configure bundle to an open access model");
+        return 0;
+    }
+    form_set_value(form, tag, "open");
+
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    Jid *jid = jid_create(connection_get_fulljid());
+    char *id = connection_create_stanza_id("omemo_bundle_node_configure_submit");
+    char *node = g_strdup_printf("%s:%d", STANZA_NS_OMEMO_BUNDLES, omemo_device_id());
+    xmpp_stanza_t *iq = stanza_create_pubsub_configure_submit(ctx, id, jid->barejid, node, form);
+    g_free(node);
+
+    iq_id_handler_add(id, _omemo_bundle_publish_configure_result, NULL, userdata);
+
+    iq_send_stanza(iq);
+
+    xmpp_stanza_release(iq);
+    free(id);
+    jid_destroy(jid);
+    return 0;
+}
+
+static int
+_omemo_bundle_publish_configure_result(xmpp_stanza_t *const stanza, void *const userdata)
+{
+    const char *type = xmpp_stanza_get_type(stanza);
+
+    if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+        log_error("OMEMO: cannot configure bundle to an open access model");
+        return 0;
+    }
+
+    omemo_bundle_publish(TRUE);
+
+    return 0;
+}
diff --git a/src/xmpp/omemo.h b/src/xmpp/omemo.h
new file mode 100644
index 00000000..f1fff7b7
--- /dev/null
+++ b/src/xmpp/omemo.h
@@ -0,0 +1,11 @@
+#include <glib.h>
+
+#include "xmpp/iq.h"
+
+void omemo_devicelist_subscribe(void);
+void omemo_devicelist_publish(GList *device_list);
+void omemo_devicelist_request(const char * const jid);
+void omemo_bundle_publish(gboolean first);
+void omemo_bundle_request(const char * const jid, uint32_t device_id, ProfIqCallback func, ProfIqFreeCallback free_func, void *userdata);
+int omemo_start_device_session_handle_bundle(xmpp_stanza_t *const stanza, void *const userdata);
+char * omemo_receive_message(xmpp_stanza_t *const stanza);
diff --git a/src/xmpp/roster.c b/src/xmpp/roster.c
index 9be154e7..fe15515f 100644
--- a/src/xmpp/roster.c
+++ b/src/xmpp/roster.c
@@ -137,7 +137,7 @@ roster_send_add_to_group(const char *const group, PContact contact)
     }
 
     xmpp_ctx_t * const ctx = connection_get_ctx();
-    iq_id_handler_add(unique_id, _group_add_id_handler, (ProfIdFreeCallback)_free_group_data, data);
+    iq_id_handler_add(unique_id, _group_add_id_handler, (ProfIqFreeCallback)_free_group_data, data);
     xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, p_contact_barejid(contact),
         p_contact_name(contact), new_groups);
     iq_send_stanza(iq);
@@ -180,7 +180,7 @@ roster_send_remove_from_group(const char *const group, PContact contact)
         data->name = strdup(p_contact_barejid(contact));
     }
 
-    iq_id_handler_add(unique_id, _group_remove_id_handler, (ProfIdFreeCallback)_free_group_data, data);
+    iq_id_handler_add(unique_id, _group_remove_id_handler, (ProfIqFreeCallback)_free_group_data, data);
     xmpp_stanza_t *iq = stanza_create_roster_set(ctx, unique_id, p_contact_barejid(contact),
         p_contact_name(contact), new_groups);
     iq_send_stanza(iq);
diff --git a/src/xmpp/session.c b/src/xmpp/session.c
index de7fb7ac..675f23af 100644
--- a/src/xmpp/session.c
+++ b/src/xmpp/session.c
@@ -60,6 +60,11 @@
 #include "xmpp/chat_session.h"
 #include "xmpp/jid.h"
 
+#ifdef HAVE_OMEMO
+#include "omemo/omemo.h"
+#include "xmpp/omemo.h"
+#endif
+
 // for auto reconnect
 static struct {
     char *name;
@@ -286,6 +291,12 @@ session_get_account_name(void)
 void
 session_login_success(gboolean secured)
 {
+    chat_sessions_init();
+
+    message_handlers_init();
+    presence_handlers_init();
+    iq_handlers_init();
+
     // logged in with account
     if (saved_account.name) {
         log_debug("Connection handler: logged in with account name: %s", saved_account.name);
@@ -297,26 +308,20 @@ session_login_success(gboolean secured)
         accounts_add(saved_details.name, saved_details.altdomain, saved_details.port, saved_details.tls_policy);
         accounts_set_jid(saved_details.name, saved_details.jid);
 
-        sv_ev_login_account_success(saved_details.name, secured);
         saved_account.name = strdup(saved_details.name);
         saved_account.passwd = strdup(saved_details.passwd);
 
         _session_free_saved_details();
+        sv_ev_login_account_success(saved_account.name, secured);
     }
 
-    chat_sessions_init();
-
-    message_handlers_init();
-    presence_handlers_init();
-    iq_handlers_init();
-
     roster_request();
     bookmark_request();
     blocking_request();
 
     // items discovery
+    connection_request_features();
     char *domain = connection_get_domain();
-    iq_disco_info_request_onconnect(domain);
     iq_disco_items_request_onconnect(domain);
 
     if (prefs_get_boolean(PREF_CARBONS)){
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 534ee06b..615de44f 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -396,6 +396,18 @@ stanza_attach_hints_no_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
 }
 
 xmpp_stanza_t*
+stanza_attach_hints_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
+{
+    xmpp_stanza_t *store = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(store, "store");
+    xmpp_stanza_set_ns(store, STANZA_NS_HINTS);
+    xmpp_stanza_add_child(stanza, store);
+    xmpp_stanza_release(store);
+
+    return stanza;
+}
+
+xmpp_stanza_t*
 stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza)
 {
     xmpp_stanza_t *receipet_request = xmpp_stanza_new(ctx);
@@ -1821,6 +1833,45 @@ stanza_get_error_message(xmpp_stanza_t *stanza)
 }
 
 void
+stanza_attach_publish_options(xmpp_ctx_t *const ctx, xmpp_stanza_t *const iq, const char *const option, const char *const value)
+{
+    xmpp_stanza_t *publish_options = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(publish_options, STANZA_NAME_PUBLISH_OPTIONS);
+
+    xmpp_stanza_t *x = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(x, STANZA_NAME_X);
+    xmpp_stanza_set_ns(x, STANZA_NS_DATA);
+    xmpp_stanza_set_type(x, "submit");
+    xmpp_stanza_add_child(publish_options, x);
+
+    xmpp_stanza_t *form_type = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(form_type, STANZA_NAME_FIELD);
+    xmpp_stanza_set_attribute(form_type, STANZA_ATTR_VAR, "FORM_TYPE");
+    xmpp_stanza_set_type(form_type, "hidden");
+    xmpp_stanza_t *form_type_value = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(form_type_value, STANZA_NAME_VALUE);
+    xmpp_stanza_t *form_type_value_text = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_text(form_type_value_text, XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS);
+    xmpp_stanza_add_child(form_type_value, form_type_value_text);
+    xmpp_stanza_add_child(form_type, form_type_value);
+    xmpp_stanza_add_child(x, form_type);
+
+    xmpp_stanza_t *access_model = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(access_model, STANZA_NAME_FIELD);
+    xmpp_stanza_set_attribute(access_model, STANZA_ATTR_VAR, option);
+    xmpp_stanza_t *access_model_value = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(access_model_value, STANZA_NAME_VALUE);
+    xmpp_stanza_t *access_model_value_text = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_text(access_model_value_text, value);
+    xmpp_stanza_add_child(access_model_value, access_model_value_text);
+    xmpp_stanza_add_child(access_model, access_model_value);
+    xmpp_stanza_add_child(x, access_model);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_get_child_by_ns(iq, STANZA_NS_PUBSUB);
+    xmpp_stanza_add_child(pubsub, publish_options);
+}
+
+void
 stanza_attach_priority(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence, const int pri)
 {
     if (pri == 0) {
@@ -2092,6 +2143,295 @@ stanza_create_command_config_submit_iq(xmpp_ctx_t *ctx, const char *const room,
     return iq;
 }
 
+xmpp_stanza_t*
+stanza_create_omemo_devicelist_request(xmpp_ctx_t *ctx, const char *const id,
+    const char *const jid)
+{
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id);
+    xmpp_stanza_set_to(iq, jid);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t *items = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(items, "items");
+    xmpp_stanza_set_attribute(items, STANZA_ATTR_NODE, STANZA_NS_OMEMO_DEVICELIST);
+
+    xmpp_stanza_add_child(pubsub, items);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(items);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_omemo_devicelist_subscribe(xmpp_ctx_t *ctx, const char *const jid)
+{
+    char *id = connection_create_stanza_id("omemo_devicelist_subscribe");
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t *subscribe = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(subscribe, STANZA_NAME_SUBSCRIBE);
+    xmpp_stanza_set_attribute(subscribe, STANZA_ATTR_NODE, STANZA_NS_OMEMO_DEVICELIST);
+    xmpp_stanza_set_attribute(subscribe, "jid", jid);
+
+    xmpp_stanza_add_child(pubsub, subscribe);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(subscribe);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_omemo_devicelist_publish(xmpp_ctx_t *ctx, GList *const ids)
+{
+    char *id = connection_create_stanza_id("omemo_devicelist_publish");
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    free(id);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t *publish = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH);
+    xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, STANZA_NS_OMEMO_DEVICELIST);
+
+    xmpp_stanza_t *item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    xmpp_stanza_set_attribute(item, "id", "current");
+
+    xmpp_stanza_t *list = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(list, "list");
+    xmpp_stanza_set_ns(list, "eu.siacs.conversations.axolotl");
+
+    GList *i;
+    for (i = ids; i != NULL; i = i->next) {
+        xmpp_stanza_t *device = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(device, "device");
+        char *id = g_strdup_printf("%d", GPOINTER_TO_INT(i->data));
+        xmpp_stanza_set_attribute(device, "id", id);
+        g_free(id);
+
+        xmpp_stanza_add_child(list, device);
+        xmpp_stanza_release(device);
+    }
+
+    xmpp_stanza_add_child(item, list);
+    xmpp_stanza_add_child(publish, item);
+    xmpp_stanza_add_child(pubsub, publish);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(list);
+    xmpp_stanza_release(item);
+    xmpp_stanza_release(publish);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_omemo_bundle_publish(xmpp_ctx_t *ctx, const char *const id,
+    uint32_t device_id,
+    const unsigned char * const identity_key, size_t identity_key_length,
+    const unsigned char * const signed_prekey, size_t signed_prekey_length,
+    const unsigned char * const signed_prekey_signature, size_t signed_prekey_signature_length,
+    GList *const prekeys, GList *const prekeys_id, GList *const prekeys_length)
+{
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t *publish = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(publish, STANZA_NAME_PUBLISH);
+    char *node = g_strdup_printf("%s:%d", "eu.siacs.conversations.axolotl.bundles", device_id);
+    xmpp_stanza_set_attribute(publish, STANZA_ATTR_NODE, node);
+    g_free(node);
+
+    xmpp_stanza_t *item = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(item, STANZA_NAME_ITEM);
+    xmpp_stanza_set_attribute(item, "id", "current");
+
+    xmpp_stanza_t *bundle = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(bundle, "bundle");
+    xmpp_stanza_set_ns(bundle, "eu.siacs.conversations.axolotl");
+
+    xmpp_stanza_t *signed_prekey_public_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(signed_prekey_public_stanza , "signedPreKeyPublic");
+    xmpp_stanza_set_attribute(signed_prekey_public_stanza, "signedPreKeyId", "1");
+
+    xmpp_stanza_t *signed_prekey_public_stanza_text= xmpp_stanza_new(ctx);
+    char *signed_prekey_b64 = g_base64_encode(signed_prekey, signed_prekey_length);
+    xmpp_stanza_set_text(signed_prekey_public_stanza_text, signed_prekey_b64);
+    g_free(signed_prekey_b64);
+    xmpp_stanza_add_child(signed_prekey_public_stanza, signed_prekey_public_stanza_text);
+    xmpp_stanza_release(signed_prekey_public_stanza_text);
+
+    xmpp_stanza_t *signed_prekey_signature_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(signed_prekey_signature_stanza , "signedPreKeySignature");
+
+    xmpp_stanza_t *signed_prekey_signature_stanza_text= xmpp_stanza_new(ctx);
+    char *signed_prekey_signature_b64 = g_base64_encode(signed_prekey_signature, signed_prekey_signature_length);
+    xmpp_stanza_set_text(signed_prekey_signature_stanza_text, signed_prekey_signature_b64);
+    g_free(signed_prekey_signature_b64);
+    xmpp_stanza_add_child(signed_prekey_signature_stanza, signed_prekey_signature_stanza_text);
+    xmpp_stanza_release(signed_prekey_signature_stanza_text);
+
+    xmpp_stanza_t *identity_key_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(identity_key_stanza , "identityKey");
+
+    xmpp_stanza_t *identity_key_stanza_text= xmpp_stanza_new(ctx);
+    char *identity_key_b64 = g_base64_encode(identity_key, identity_key_length);
+    xmpp_stanza_set_text(identity_key_stanza_text, identity_key_b64);
+    g_free(identity_key_b64);
+    xmpp_stanza_add_child(identity_key_stanza, identity_key_stanza_text);
+    xmpp_stanza_release(identity_key_stanza_text);
+
+    xmpp_stanza_t *prekeys_stanza = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(prekeys_stanza, "prekeys");
+
+    GList *p, *i, *l;
+    for (p = prekeys, i = prekeys_id, l = prekeys_length; p != NULL; p = p->next, i = i->next, l = l->next) {
+        xmpp_stanza_t *prekey = xmpp_stanza_new(ctx);
+        xmpp_stanza_set_name(prekey, "preKeyPublic");
+        char *id = g_strdup_printf("%d", GPOINTER_TO_INT(i->data));
+        xmpp_stanza_set_attribute(prekey, "preKeyId", id);
+        g_free(id);
+
+        xmpp_stanza_t *prekey_text = xmpp_stanza_new(ctx);
+        char *prekey_b64 = g_base64_encode(p->data, GPOINTER_TO_INT(l->data));
+        xmpp_stanza_set_text(prekey_text, prekey_b64);
+        g_free(prekey_b64);
+
+        xmpp_stanza_add_child(prekey, prekey_text);
+        xmpp_stanza_add_child(prekeys_stanza, prekey);
+        xmpp_stanza_release(prekey_text);
+        xmpp_stanza_release(prekey);
+    }
+
+    xmpp_stanza_add_child(bundle, signed_prekey_public_stanza);
+    xmpp_stanza_add_child(bundle, signed_prekey_signature_stanza);
+    xmpp_stanza_add_child(bundle, identity_key_stanza);
+    xmpp_stanza_add_child(bundle, prekeys_stanza);
+    xmpp_stanza_add_child(item, bundle);
+    xmpp_stanza_add_child(publish, item);
+    xmpp_stanza_add_child(pubsub, publish);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(signed_prekey_public_stanza);
+    xmpp_stanza_release(signed_prekey_signature_stanza);
+    xmpp_stanza_release(identity_key_stanza);
+    xmpp_stanza_release(prekeys_stanza);
+    xmpp_stanza_release(bundle);
+    xmpp_stanza_release(item);
+    xmpp_stanza_release(publish);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_omemo_bundle_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, uint32_t device_id)
+{
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id);
+    xmpp_stanza_set_to(iq, jid);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB);
+
+    xmpp_stanza_t *items = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(items, "items");
+    char *node = g_strdup_printf("%s:%d", STANZA_NS_OMEMO_BUNDLES, device_id);
+    xmpp_stanza_set_attribute(items, STANZA_ATTR_NODE, node);
+    g_free(node);
+
+    xmpp_stanza_add_child(pubsub, items);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(items);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_pubsub_configure_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, const char *const node)
+{
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_GET, id);
+    xmpp_stanza_set_to(iq, jid);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB_OWNER);
+
+    xmpp_stanza_t *configure = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(configure, STANZA_NAME_CONFIGURE);
+    xmpp_stanza_set_attribute(configure, STANZA_ATTR_NODE, node);
+
+    xmpp_stanza_add_child(pubsub, configure);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(configure);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_create_pubsub_configure_submit(xmpp_ctx_t *ctx, const char *const id, const char *const jid, const char *const node, DataForm *form)
+{
+    xmpp_stanza_t *iq = xmpp_iq_new(ctx, STANZA_TYPE_SET, id);
+    xmpp_stanza_set_to(iq, jid);
+
+    xmpp_stanza_t *pubsub = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(pubsub, STANZA_NAME_PUBSUB);
+    xmpp_stanza_set_ns(pubsub, STANZA_NS_PUBSUB_OWNER);
+
+    xmpp_stanza_t *configure = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(configure, STANZA_NAME_CONFIGURE);
+    xmpp_stanza_set_attribute(configure, STANZA_ATTR_NODE, node);
+
+    xmpp_stanza_t *x = form_create_submission(form);
+
+    xmpp_stanza_add_child(configure, x);
+    xmpp_stanza_add_child(pubsub, configure);
+    xmpp_stanza_add_child(iq, pubsub);
+
+    xmpp_stanza_release(x);
+    xmpp_stanza_release(configure);
+    xmpp_stanza_release(pubsub);
+
+    return iq;
+}
+
+xmpp_stanza_t*
+stanza_attach_origin_id(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const id)
+{
+    xmpp_stanza_t *origin_id = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(origin_id, STANZA_NAME_ORIGIN_ID);
+    xmpp_stanza_set_ns(origin_id, STANZA_NS_STABLE_ID);
+    xmpp_stanza_set_attribute(origin_id, STANZA_ATTR_ID, id);
+
+    xmpp_stanza_add_child(stanza, origin_id);
+
+    xmpp_stanza_release(origin_id);
+
+    return stanza;
+}
+
 static void
 _stanza_add_unique_id(xmpp_stanza_t *stanza, char *prefix)
 {
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index d3c3c9dc..e5e17ba4 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -82,6 +82,7 @@
 #define STANZA_NAME_PUBSUB "pubsub"
 #define STANZA_NAME_PUBLISH "publish"
 #define STANZA_NAME_PUBLISH_OPTIONS "publish-options"
+#define STANZA_NAME_SUBSCRIBE "subscribe"
 #define STANZA_NAME_FIELD "field"
 #define STANZA_NAME_STORAGE "storage"
 #define STANZA_NAME_NICK "nick"
@@ -100,6 +101,8 @@
 #define STANZA_NAME_GET "get"
 #define STANZA_NAME_URL "url"
 #define STANZA_NAME_COMMAND "command"
+#define STANZA_NAME_CONFIGURE "configure"
+#define STANZA_NAME_ORIGIN_ID "origin-id"
 
 // error conditions
 #define STANZA_NAME_BAD_REQUEST "bad-request"
@@ -179,6 +182,8 @@
 #define STANZA_NS_CONFERENCE "jabber:x:conference"
 #define STANZA_NS_CAPTCHA "urn:xmpp:captcha"
 #define STANZA_NS_PUBSUB "http://jabber.org/protocol/pubsub"
+#define STANZA_NS_PUBSUB_OWNER "http://jabber.org/protocol/pubsub#owner"
+#define STANZA_NS_PUBSUB_EVENT "http://jabber.org/protocol/pubsub#event"
 #define STANZA_NS_CARBONS "urn:xmpp:carbons:2"
 #define STANZA_NS_HINTS "urn:xmpp:hints"
 #define STANZA_NS_FORWARD "urn:xmpp:forward:0"
@@ -189,6 +194,10 @@
 #define STANZA_NS_X_OOB "jabber:x:oob"
 #define STANZA_NS_BLOCKING "urn:xmpp:blocking"
 #define STANZA_NS_COMMAND "http://jabber.org/protocol/commands"
+#define STANZA_NS_OMEMO "eu.siacs.conversations.axolotl"
+#define STANZA_NS_OMEMO_DEVICELIST "eu.siacs.conversations.axolotl.devicelist"
+#define STANZA_NS_OMEMO_BUNDLES "eu.siacs.conversations.axolotl.bundles"
+#define STANZA_NS_STABLE_ID "urn:xmpp:sid:0"
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
@@ -228,8 +237,10 @@ xmpp_stanza_t* stanza_attach_state(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const
 xmpp_stanza_t* stanza_attach_carbons_private(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
 xmpp_stanza_t* stanza_attach_hints_no_copy(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
 xmpp_stanza_t* stanza_attach_hints_no_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
+xmpp_stanza_t* stanza_attach_hints_store(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
 xmpp_stanza_t* stanza_attach_receipt_request(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza);
 xmpp_stanza_t* stanza_attach_x_oob_url(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const url);
+xmpp_stanza_t* stanza_attach_origin_id(xmpp_ctx_t *ctx, xmpp_stanza_t *stanza, const char *const id);
 
 xmpp_stanza_t* stanza_create_room_join_presence(xmpp_ctx_t *const ctx,
     const char *const full_room_jid, const char *const passwd);
@@ -284,6 +295,17 @@ xmpp_stanza_t* stanza_create_room_kick_iq(xmpp_ctx_t *const ctx, const char *con
 xmpp_stanza_t* stanza_create_command_exec_iq(xmpp_ctx_t *ctx, const char *const target, const char *const node);
 xmpp_stanza_t* stanza_create_command_config_submit_iq(xmpp_ctx_t *ctx, const char *const room, const char *const node, const char *const sessionid, DataForm *form);
 
+void stanza_attach_publish_options(xmpp_ctx_t *const ctx, xmpp_stanza_t *const publish, const char *const option, const char *const value);
+
+xmpp_stanza_t* stanza_create_omemo_devicelist_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid);
+xmpp_stanza_t* stanza_create_omemo_devicelist_subscribe(xmpp_ctx_t *ctx, const char *const jid);
+xmpp_stanza_t* stanza_create_omemo_devicelist_publish(xmpp_ctx_t *ctx, GList *const ids);
+xmpp_stanza_t* stanza_create_omemo_bundle_publish(xmpp_ctx_t *ctx, const char *const id, uint32_t device_id, const unsigned char * const identity_key, size_t identity_key_length, const unsigned char * const signed_prekey, size_t signed_prekey_length, const unsigned char * const signed_prekey_signature, size_t signed_prekey_signature_length, GList *const prekeys, GList *const prekeys_id, GList *const prekeys_length);
+xmpp_stanza_t* stanza_create_omemo_bundle_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, uint32_t device_id);
+
+xmpp_stanza_t* stanza_create_pubsub_configure_request(xmpp_ctx_t *ctx, const char *const id, const char *const jid, const char *const node);
+xmpp_stanza_t* stanza_create_pubsub_configure_submit(xmpp_ctx_t *ctx, const char *const id, const char *const jid, const char *const node, DataForm *form);
+
 int stanza_get_idle_time(xmpp_stanza_t *const stanza);
 
 void stanza_attach_priority(xmpp_ctx_t *const ctx, xmpp_stanza_t *const presence, const int pri);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index c9403090..d5330599 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -35,6 +35,8 @@
 #ifndef XMPP_XMPP_H
 #define XMPP_XMPP_H
 
+#include <stdint.h>
+
 #include "config.h"
 
 #ifdef HAVE_LIBMESODE
@@ -61,6 +63,9 @@
 #define XMPP_FEATURE_LASTACTIVITY "jabber:iq:last"
 #define XMPP_FEATURE_MUC "http://jabber.org/protocol/muc"
 #define XMPP_FEATURE_COMMANDS "http://jabber.org/protocol/commands"
+#define XMPP_FEATURE_OMEMO_DEVICELIST_NOTIFY "eu.siacs.conversations.axolotl.devicelist+notify"
+#define XMPP_FEATURE_PUBSUB "http://jabber.org/protocol/pubsub"
+#define XMPP_FEATURE_PUBSUB_PUBLISH_OPTIONS "http://jabber.org/protocol/pubsub#publish-options"
 
 typedef enum {
     JABBER_CONNECTING,
@@ -139,8 +144,9 @@ char* message_send_chat(const char *const barejid, const char *const msg, const
     gboolean request_receipt);
 char* message_send_chat_otr(const char *const barejid, const char *const msg, gboolean request_receipt);
 char* message_send_chat_pgp(const char *const barejid, const char *const msg, gboolean request_receipt);
+char* message_send_chat_omemo(const char *const jid, uint32_t sid, GList *keys, const unsigned char *const iv, size_t iv_len, const unsigned char *const ciphertext, size_t ciphertext_len, gboolean request_receipt, gboolean muc);
 void message_send_private(const char *const fulljid, const char *const msg, const char *const oob_url);
-void message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url);
+char* message_send_groupchat(const char *const roomjid, const char *const msg, const char *const oob_url);
 void message_send_groupchat_subject(const char *const roomjid, const char *const subject);
 void message_send_inactive(const char *const jid);
 void message_send_composing(const char *const jid);