diff --git a/.envrc b/.envrc index 7da69a7..64641e9 100644 --- a/.envrc +++ b/.envrc @@ -5,10 +5,12 @@ export CC=gcc CXX="g++ -fdiagnostics-color=always" # Miscellaneous packages. ENVIRONMENTS=( weechat # Debug runs + profanity # Test ) # Environment packages. PACKAGES=( + profanity # Test autoconf # Deps with autoreconf autoconf-archive # Deps with m4 tooling automake # Deps with automake @@ -25,7 +27,7 @@ PACKAGES=( libxml2 # Dep (libxml2) libstrophe # Dep (strophe) libgcrypt # Dep (gcrypt) - libsignal-protocol-c # Dep (libsignal) + libsignal-protocol-c -l libomemo.scm # Dep (libsignal) lmdb lmdbxx # Dep (lmdb) rnp # Dep (rnpgp) ) @@ -34,11 +36,17 @@ echo direnv: fetching source - weechat mkdir -p /tmp/guix-build-weechat-3.2.drv-0 tar -C /tmp/guix-build-weechat-3.2.drv-0 -xJf $(guix build --source weechat) +echo direnv: fetching source - libomemo-c +mkdir -p /tmp/guix-build-libomemo-c-2.3.3.drv-0 +ln -sf $(guix build --source -f libomemo.scm) /tmp/guix-build-libomemo-c-2.3.3.drv-0 + use guix \ ${ENVIRONMENTS[@]} --ad-hoc ${PACKAGES[@]} \ + --with-debug-info=profanity\ --with-debug-info=weechat \ --with-debug-info=libstrophe \ --with-debug-info=libsignal-protocol-c \ + --with-debug-info=libomemo-c\ --with-debug-info=lmdb \ --with-debug-info=rnp \ clang clang:extra ccls gdb diff --git a/README.org b/README.org index 200c7c1..84aa336 100644 --- a/README.org +++ b/README.org @@ -16,7 +16,7 @@ [[https://github.com/bqv/weechat-xmpp/issues?q=is:issue+is:closed][file:https://img.shields.io/github/issues-closed/bqv/weechat-xmpp.svg]] [[https://github.com/bqv/weechat-xmpp/blob/master/LICENSE][file:https://img.shields.io/github/license/bqv/weechat-xmpp.svg]] [[https://github.com/bqv/weechat-extras/][file:https://img.shields.io/badge/weechat--extras-xmpp-blue.svg]] -[[https://github.com/bqv/weechat-extras/][file:https://inverse.chat/badge.svg?room=weechat@muc.xa0.uk]] +[[https://inverse.chat/#converse/room?jid=weechat@muc.xa0.uk][file:https://inverse.chat/badge.svg?room=weechat@muc.xa0.uk]] | Status: | XMPP for power users and digital masochists | | Location: | [[http://github.com/bqv/weechat-xmpp]] | @@ -31,14 +31,14 @@ My priority here is to have an android-available XMPP client that hides as little as possible from the user. To use this with android, set up a relay (`/relay`) and see weechat-android. - I'm gonna rewrite this in C++ at some point when I have a feel - for the full behaviour of an average client. + I might rewrite this in C++ at some point when I feel like I + can do it without burning out. * Usage - 1. Start with =/xmpp add=, use =/help xmpp= for instructions. + 1. Start with =/account add=, use =/help account= for instructions. - 2. Use =/xmpp connect = with the name set at + 2. Use =/account connect = with the name set at add-time. * Installing @@ -49,7 +49,8 @@ - libstrophe (dynamic, dependency) - libxml2 (dynamic, dependency) - - libsignal-protocol-c (dynamic, dependency) + - lmdb (dynamic, dependency) + - libomemo-c (libsignal-protocol-c) (dynamic, dependency) - rnp (dynamic, dependency) - weechat (>= v3.0) @@ -95,6 +96,7 @@ * [-] OOB messages * [X] Single media on a line * [ ] Multiple media inline (protocol?) + * [ ] Encrypted (pgp/omemo) * [X] Buffer autoswitch on enter/open * [X] Handle open/enter jids with a resource without breaking * [X] Allow /close without crashing @@ -119,18 +121,18 @@ * [X] [#B] Leaves * [X] [#B] Tracking * [X] [#B] Set/show topic - * [-] OMEMO (libsignal-protocol-c) - * [-] Presence + * [X] OMEMO (libomemo-c) + * [X] Presence * [X] Disco * [X] Disco response - * [-] Key Generation / storage (lmdb) + * [X] Key Generation / storage (lmdb) * [X] Generation - * [?] Storage - * [-] Announce + * [X] Storage + * [X] Announce * [X] Device ID - * [ ] Bundles - * [ ] Messages - * [ ] [#C] MUC PMs + * [X] Bundles + * [X] Messages + * [X] [#C] MUC PMs * [X] [#A] Send typing notifications * [X] [#A] Recv typing notifications * [X] [#C] Read receipts @@ -156,7 +158,6 @@ * [X] Decryption * [X] Encryption * [X] Custom set/clear key (/pgp) - * [ ] OOB data and media * [ ] Room Explorer (https://search.jabber.network/docs/api) ** TODO [#C] Implement completion engine (milestone v0.3) ** TODO [#D] Close all issues (milestone v1.0) @@ -167,7 +168,7 @@ Please submit a pull request or create an issue to add a new or missing feature. -* Testemonials +* Testimonials "Weechat-Strophe - for the discerning dual IRCer XMPPer" -- [[github.com/janicez][Ellenor et al Bjornsdottir]] diff --git a/account.c b/account.c index 2e6f413..92718c8 100644 --- a/account.c +++ b/account.c @@ -11,6 +11,7 @@ #include #include "plugin.h" +#include "xmpp/stanza.h" #include "config.h" #include "input.h" #include "omemo.h" @@ -89,7 +90,7 @@ int account__search_option(const char *option_name) return -1; } -struct t_account_device *account__search_device(struct t_account *account, int id) +struct t_account_device *account__search_device(struct t_account *account, uint32_t id) { struct t_account_device *ptr_device; @@ -117,6 +118,7 @@ void account__add_device(struct t_account *account, new_device = malloc(sizeof(*new_device)); new_device->id = device->id; new_device->name = strdup(device->name); + new_device->label = device->label ? strdup(device->label) : NULL; new_device->prev_device = account->last_device; new_device->next_device = NULL; @@ -150,6 +152,8 @@ void account__free_device(struct t_account *account, struct t_account_device *de (device->next_device)->prev_device = device->prev_device; /* free device data */ + if (device->label) + free(device->label); if (device->name) free(device->name); @@ -164,6 +168,59 @@ void account__free_device_all(struct t_account *account) account__free_device(account, account->devices); } +xmpp_stanza_t *account__get_devicelist(struct t_account *account) +{ + xmpp_stanza_t *parent, **children; + struct t_account_device *device; + const char *ns, *node; + char id[64] = {0}; + int i = 0; + + device = malloc(sizeof(struct t_account_device)); + + device->id = account->omemo->device_id; + snprintf(id, sizeof(id), "%u", device->id); + device->name = strdup(id); + device->label = strdup("weechat"); + + children = malloc(sizeof(xmpp_stanza_t *) * 128); + children[i++] = stanza__iq_pubsub_publish_item_list_device( + account->context, NULL, with_noop(device->name), NULL); + + free(device->label); + free(device->name); + free(device); + + for (device = account->devices; device; + device = device->next_device) + { + if (device->id != account->omemo->device_id) + children[i++] = stanza__iq_pubsub_publish_item_list_device( + account->context, NULL, with_noop(device->name), NULL); + } + + children[i] = NULL; + node = "eu.siacs.conversations.axolotl"; + children[0] = stanza__iq_pubsub_publish_item_list( + account->context, NULL, children, with_noop(node)); + children[1] = NULL; + children[0] = stanza__iq_pubsub_publish_item( + account->context, NULL, children, with_noop("current")); + node = "eu.siacs.conversations.axolotl.devicelist"; + children[0] = stanza__iq_pubsub_publish(account->context, + NULL, children, + with_noop(node)); + ns = "http://jabber.org/protocol/pubsub"; + children[0] = stanza__iq_pubsub(account->context, NULL, + children, with_noop(ns)); + parent = stanza__iq(account->context, NULL, + children, NULL, strdup("announce1"), + NULL, NULL, strdup("set")); + free(children); + + return parent; +} + struct t_account_mam_query *account__add_mam_query(struct t_account *account, struct t_channel *channel, const char *id, @@ -692,7 +749,7 @@ int account__timer_cb(const void *pointer, void *data, int remaining_calls) struct t_account *ptr_account; for (ptr_account = accounts; ptr_account; - ptr_account = ptr_account->next_account) + ptr_account = ptr_account ? ptr_account->next_account : NULL) { if (ptr_account->is_connected && (xmpp_conn_is_connecting(ptr_account->connection) diff --git a/account.h b/account.h index 4234749..321afd4 100644 --- a/account.h +++ b/account.h @@ -66,8 +66,9 @@ enum t_account_option struct t_account_device { - int id; + uint32_t id; char *name; + char *label; struct t_account_device *prev_device; struct t_account_device *next_device; @@ -128,10 +129,11 @@ extern char *account_options[][2]; struct t_account *account__search(const char *account_name); struct t_account *account__casesearch (const char *account_name); int account__search_option(const char *option_name); -struct t_account_device *account__search_device(struct t_account *account, int id); +struct t_account_device *account__search_device(struct t_account *account, uint32_t id); void account__add_device(struct t_account *account, struct t_account_device *device); void account__free_device(struct t_account *account, struct t_account_device *device); void account__free_device_all(struct t_account *account); +xmpp_stanza_t *account__get_devicelist(struct t_account *account); struct t_account_mam_query *account__add_mam_query(struct t_account *account, struct t_channel *channel, const char *id, diff --git a/buffer.c b/buffer.c index a3447ef..7c5d69a 100644 --- a/buffer.c +++ b/buffer.c @@ -2,6 +2,7 @@ // License, version 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#include #include #include diff --git a/channel.c b/channel.c index 04ccc7c..7784250 100644 --- a/channel.c +++ b/channel.c @@ -10,29 +10,61 @@ #include #include "plugin.h" -#include "omemo.h" #include "account.h" +#include "omemo.h" #include "user.h" #include "channel.h" #include "input.h" #include "buffer.h" #include "pgp.h" +#include "util.h" const char *channel__transport_name(enum t_channel_transport transport) { switch (transport) { - case CHANNEL_TRANSPORT_PLAINTEXT: + case CHANNEL_TRANSPORT_PLAIN: return "PLAINTEXT"; case CHANNEL_TRANSPORT_OMEMO: return "OMEMO"; case CHANNEL_TRANSPORT_PGP: return "PGP"; + case CHANNEL_TRANSPORT_OTR: + return "OTR"; default: return NULL; } } +void channel__set_transport(struct t_channel *channel, enum t_channel_transport transport, int force) +{ + if (force) + switch (transport) + { + case CHANNEL_TRANSPORT_PLAIN: + channel->omemo.enabled = 0; + channel->pgp.enabled = 0; + break; + case CHANNEL_TRANSPORT_OMEMO: + channel->omemo.enabled = 1; + channel->pgp.enabled = 0; + break; + case CHANNEL_TRANSPORT_PGP: + channel->omemo.enabled = 0; + channel->pgp.enabled = 1; + break; + default: + break; + } + if (channel->transport != transport) + { + channel->transport = transport; + weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s", + weechat_prefix("network"), weechat_color("gray"), + channel__transport_name(channel->transport)); + } +} + struct t_account *channel__account(struct t_channel *channel) { struct t_account *ptr_account; @@ -80,7 +112,7 @@ struct t_gui_buffer *channel__search_buffer(struct t_account *account, { struct t_hdata *hdata_buffer; struct t_gui_buffer *ptr_buffer; - const char *ptr_type, *ptr_account_name, *ptr_channel_name; + const char *ptr_type, *ptr_account_name, *ptr_remote_jid; hdata_buffer = weechat_hdata_get("buffer"); ptr_buffer = weechat_hdata_get_list(hdata_buffer, "gui_buffers"); @@ -91,18 +123,18 @@ struct t_gui_buffer *channel__search_buffer(struct t_account *account, { ptr_type = weechat_buffer_get_string(ptr_buffer, "localvar_type"); ptr_account_name = weechat_buffer_get_string(ptr_buffer, - "localvar_server"); - ptr_channel_name = weechat_buffer_get_string(ptr_buffer, - "localvar_channel"); + "localvar_account"); + ptr_remote_jid = weechat_buffer_get_string(ptr_buffer, + "localvar_remote_jid"); if (ptr_type && ptr_type[0] && ptr_account_name && ptr_account_name[0] - && ptr_channel_name && ptr_channel_name[0] + && ptr_remote_jid && ptr_remote_jid[0] && ( (( (type == CHANNEL_TYPE_MUC)) - && (strcmp(ptr_type, "channel") == 0)) + && (strcmp(ptr_type, "room") == 0)) || (( (type == CHANNEL_TYPE_PM)) && (strcmp(ptr_type, "private") == 0))) && (strcmp(ptr_account_name, account->name) == 0) - && (weechat_strcasecmp(ptr_channel_name, name) == 0)) + && (weechat_strcasecmp(ptr_remote_jid, name) == 0)) { return ptr_buffer; } @@ -119,13 +151,13 @@ struct t_gui_buffer *channel__create_buffer(struct t_account *account, { struct t_gui_buffer *ptr_buffer; int buffer_created; - const char *short_name, *localvar_channel; - char buffer_name[256]; + const char *short_name, *localvar_remote_jid; + char buffer_name[1024]; buffer_created = 0; snprintf(buffer_name, sizeof(buffer_name), - "xmpp.%s.%s", account->name, name); + "%s.%s", account->name, name); ptr_buffer = channel__search_buffer(account, type, name); if (ptr_buffer) @@ -145,17 +177,19 @@ struct t_gui_buffer *channel__create_buffer(struct t_account *account, if (buffer_created) { + char *res = strrchr(name, '/'); if (!weechat_buffer_get_integer(ptr_buffer, "short_name_is_set")) - weechat_buffer_set(ptr_buffer, "short_name", name); + weechat_buffer_set(ptr_buffer, "short_name", + res ? res + 1 : name); } else { - short_name = weechat_buffer_get_string (ptr_buffer, "short_name"); - localvar_channel = weechat_buffer_get_string(ptr_buffer, - "localvar_channel"); + short_name = weechat_buffer_get_string(ptr_buffer, "short_name"); + localvar_remote_jid = weechat_buffer_get_string(ptr_buffer, + "localvar_remote_jid"); if (!short_name || - (localvar_channel && (strcmp(localvar_channel, short_name) == 0))) + (localvar_remote_jid && (strcmp(localvar_remote_jid, short_name) == 0))) { weechat_buffer_set(ptr_buffer, "short_name", xmpp_jid_node(account->context, name)); @@ -165,20 +199,21 @@ struct t_gui_buffer *channel__create_buffer(struct t_account *account, account_option_set(account, ACCOUNT_OPTION_NICKNAME, xmpp_jid_node(account->context, account_jid(account))); - weechat_buffer_set(ptr_buffer, "name", name); + weechat_buffer_set(ptr_buffer, "notify", + (type == CHANNEL_TYPE_PM) ? "3" : "1"); weechat_buffer_set(ptr_buffer, "localvar_set_type", (type == CHANNEL_TYPE_PM) ? "private" : "channel"); weechat_buffer_set(ptr_buffer, "localvar_set_nick", account_nickname(account)); - weechat_buffer_set(ptr_buffer, "localvar_set_server", account->name); - weechat_buffer_set(ptr_buffer, "localvar_set_channel", name); + weechat_buffer_set(ptr_buffer, "localvar_set_account", account->name); + weechat_buffer_set(ptr_buffer, "localvar_set_remote_jid", name); weechat_buffer_set(ptr_buffer, "input_multiline", "1"); if (buffer_created) { - (void) weechat_hook_signal_send ("logger_backlog", - WEECHAT_HOOK_SIGNAL_POINTER, - ptr_buffer); + (void) weechat_hook_signal_send("logger_backlog", + WEECHAT_HOOK_SIGNAL_POINTER, + ptr_buffer); weechat_buffer_set(ptr_buffer, "input_get_unknown_commands", "1"); if (type != CHANNEL_TYPE_PM) { @@ -271,8 +306,15 @@ struct t_channel *channel__new(struct t_account *account, new_channel->type = type; new_channel->id = strdup(id); new_channel->name = strdup(name); - new_channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; - new_channel->pgp_id = NULL; + new_channel->transport = CHANNEL_TRANSPORT_PLAIN; + new_channel->omemo.enabled = type == CHANNEL_TYPE_PM ? 1 : 0; + new_channel->omemo.devicelist_requests = weechat_hashtable_new(64, + WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_POINTER, NULL, NULL); + new_channel->omemo.bundle_requests = weechat_hashtable_new(64, + WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_POINTER, NULL, NULL); + new_channel->pgp.enabled = 1; + new_channel->pgp.id = NULL; + new_channel->otr.enabled = 0; new_channel->topic.value = NULL; new_channel->topic.creator = NULL; @@ -788,8 +830,8 @@ void channel__free(struct t_account *account, free(channel->id); if (channel->name) free(channel->name); - if (channel->pgp_id) - free(channel->pgp_id); + if (channel->pgp.id) + free(channel->pgp.id); if (channel->topic.value) free(channel->topic.value); if (channel->topic.creator) @@ -914,10 +956,13 @@ struct t_channel_member *channel__add_member(struct t_account *account, user->profile.status_text ? " [" : "", user->profile.status_text ? user->profile.status_text : "", user->profile.status_text ? "]" : "", - user->profile.pgp_id ? weechat_color("gray") : "", - user->profile.pgp_id ? " with PGP:" : "", + user->profile.pgp_id || user->profile.omemo ? weechat_color("gray") : "", + user->profile.pgp_id || user->profile.omemo ? " with " : "", + user->profile.pgp_id ? "PGP:" : "", user->profile.pgp_id ? user->profile.pgp_id : "", - user->profile.pgp_id ? weechat_color("reset") : ""); + user->profile.omemo && user->profile.pgp_id ? " and " : "", + user->profile.omemo ? "OMEMO" : "", + user->profile.pgp_id || user->profile.omemo ? weechat_color("reset") : ""); return member; } @@ -983,8 +1028,8 @@ struct t_channel_member *channel__remove_member(struct t_account *account, return member; } -void channel__send_message(struct t_account *account, struct t_channel *channel, - const char *to, const char *body) +int channel__send_message(struct t_account *account, struct t_channel *channel, + const char *to, const char *body) { channel__send_reads(account, channel); @@ -997,17 +1042,51 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, xmpp_stanza_set_id(message, id); xmpp_free(account->context, id); - char *url = strstr(body, "http"); - if (channel->pgp_id) + if (account->omemo && channel->omemo.enabled) + { + xmpp_stanza_t *encrypted = omemo__encode(account, to, body); + if (!encrypted) + { + weechat_printf_date_tags(channel->buffer, 0, "notify_none", "%s%s", + weechat_prefix("error"), "OMEMO Error"); + channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 1); + xmpp_stanza_release(message); + return WEECHAT_RC_ERROR; + } + xmpp_stanza_add_child(message, encrypted); + xmpp_stanza_release(encrypted); + + xmpp_stanza_t *message__encryption = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__encryption, "encryption"); + xmpp_stanza_set_ns(message__encryption, "urn:xmpp:eme:0"); + xmpp_stanza_set_attribute(message__encryption, "namespace", + "eu.siacs.conversations.axolotl"); + xmpp_stanza_set_attribute(message__encryption, "name", "OMEMO"); + xmpp_stanza_add_child(message, message__encryption); + xmpp_stanza_release(message__encryption); + + xmpp_message_set_body(message, OMEMO_ADVICE); + + channel__set_transport(channel, CHANNEL_TRANSPORT_OMEMO, 0); + } + else if (channel->pgp.enabled && channel->pgp.id) { xmpp_stanza_t *message__x = xmpp_stanza_new(account->context); xmpp_stanza_set_name(message__x, "x"); xmpp_stanza_set_ns(message__x, "jabber:x:encrypted"); xmpp_stanza_t *message__x__text = xmpp_stanza_new(account->context); - char *ciphertext = pgp__encrypt(channel->buffer, account->pgp, account_pgp_keyid(account), channel->pgp_id, body); + char *ciphertext = pgp__encrypt(channel->buffer, account->pgp, account_pgp_keyid(account), channel->pgp.id, body); if (ciphertext) xmpp_stanza_set_text(message__x__text, ciphertext); + else + { + weechat_printf_date_tags(channel->buffer, 0, "notify_none", "%s%s", + weechat_prefix("error"), "PGP Error"); + channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 1); + xmpp_stanza_release(message); + return WEECHAT_RC_ERROR; + } free(ciphertext); xmpp_stanza_add_child(message__x, message__x__text); @@ -1026,28 +1105,17 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, xmpp_message_set_body(message, PGP_ADVICE); - if (ciphertext && channel->transport != CHANNEL_TRANSPORT_PGP) - { - channel->transport = CHANNEL_TRANSPORT_PGP; - weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s", - weechat_prefix("network"), weechat_color("gray"), - channel__transport_name(channel->transport)); - } + channel__set_transport(channel, CHANNEL_TRANSPORT_PGP, 0); } else { xmpp_message_set_body(message, body); - if (channel->transport != CHANNEL_TRANSPORT_PLAINTEXT) - { - channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; - weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s", - weechat_prefix("network"), weechat_color("gray"), - channel__transport_name(channel->transport)); - } + channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 0); } - if (url) + char *url = strstr(body, "http"); + if (url && channel->transport == CHANNEL_TRANSPORT_PLAIN) { xmpp_stanza_t *message__x = xmpp_stanza_new(account->context); xmpp_stanza_set_name(message__x, "x"); @@ -1070,11 +1138,28 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, xmpp_stanza_t *message__active = xmpp_stanza_new(account->context); xmpp_stanza_set_name(message__active, "active"); - xmpp_stanza_set_ns(message__active, "http://jabber./org/protocol/chatstates"); - + xmpp_stanza_set_ns(message__active, "http://jabber.org/protocol/chatstates"); xmpp_stanza_add_child(message, message__active); xmpp_stanza_release(message__active); + xmpp_stanza_t *message__request = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__request, "request"); + xmpp_stanza_set_ns(message__request, "urn:xmpp:receipts"); + xmpp_stanza_add_child(message, message__request); + xmpp_stanza_release(message__request); + + xmpp_stanza_t *message__markable = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__markable, "markable"); + xmpp_stanza_set_ns(message__markable, "urn:xmpp:chat-markers:0"); + xmpp_stanza_add_child(message, message__markable); + xmpp_stanza_release(message__markable); + + xmpp_stanza_t *message__store = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(message__store, "store"); + xmpp_stanza_set_ns(message__store, "urn:xmpp:hints"); + xmpp_stanza_add_child(message, message__store); + xmpp_stanza_release(message__store); + xmpp_send(account->connection, message); xmpp_stanza_release(message); if (channel->type != CHANNEL_TYPE_MUC) @@ -1083,6 +1168,8 @@ void channel__send_message(struct t_account *account, struct t_channel *channel, "%s\t%s", user__as_prefix_raw(account, account_jid(account)), body); + + return WEECHAT_RC_OK; } void channel__send_reads(struct t_account *account, struct t_channel *channel) diff --git a/channel.h b/channel.h index 37863f1..5643679 100644 --- a/channel.h +++ b/channel.h @@ -15,9 +15,11 @@ enum t_channel_type enum t_channel_transport { - CHANNEL_TRANSPORT_PLAINTEXT, + CHANNEL_TRANSPORT_PLAIN, CHANNEL_TRANSPORT_OMEMO, CHANNEL_TRANSPORT_PGP, + CHANNEL_TRANSPORT_OTR, + CHANNEL_TRANSPORT_OX, }; struct t_channel_typing @@ -63,7 +65,18 @@ struct t_channel char *id; char *name; enum t_channel_transport transport; - char *pgp_id; + struct { + int enabled; + struct t_hashtable *devicelist_requests; + struct t_hashtable *bundle_requests; + } omemo; + struct { + int enabled; + char *id; + } pgp; + struct { + int enabled; + } otr; struct t_channel_topic topic; @@ -92,6 +105,9 @@ struct t_channel const char *channel__transport_name(enum t_channel_transport transport); +void channel__set_transport(struct t_channel *channel, + enum t_channel_transport transport, int force); + struct t_account *channel__account(struct t_channel *channel); struct t_channel *channel__search(struct t_account *account, @@ -179,8 +195,8 @@ struct t_channel_member *channel__remove_member(struct t_account *account, struct t_channel *channel, const char *id, const char *reason); -void channel__send_message(struct t_account *account, struct t_channel *channel, - const char *to, const char *body); +int channel__send_message(struct t_account *account, struct t_channel *channel, + const char *to, const char *body); void channel__send_reads(struct t_account *account, struct t_channel *channel); diff --git a/command.c b/command.c index 7bbbd98..84e9722 100644 --- a/command.c +++ b/command.c @@ -4,12 +4,12 @@ #include #include +#include #include #include #include #include "plugin.h" -//#include "oauth.h" #include "account.h" #include "user.h" #include "channel.h" @@ -501,7 +501,7 @@ int command__enter(const void *pointer, void *data, } else { - const char *buffer_jid = weechat_buffer_get_string(buffer, "localvar_channel"); + const char *buffer_jid = weechat_buffer_get_string(buffer, "localvar_remote_jid"); pres_jid = xmpp_jid_new( ptr_account->context, @@ -756,6 +756,41 @@ int command__mam(const void *pointer, void *data, return WEECHAT_RC_OK; } +int command__omemo(const void *pointer, void *data, + struct t_gui_buffer *buffer, int argc, + char **argv, char **argv_eol) +{ + struct t_account *ptr_account = NULL; + struct t_channel *ptr_channel = NULL; + + (void) pointer; + (void) data; + (void) argc; + (void) argv; + (void) argv_eol; + + buffer__get_account_and_channel(buffer, &ptr_account, &ptr_channel); + + if (!ptr_account) + return WEECHAT_RC_ERROR; + + if (!ptr_channel) + { + weechat_printf( + ptr_account->buffer, + _("%s%s: \"%s\" command can not be executed on a account buffer"), + weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME, "omemo"); + return WEECHAT_RC_OK; + } + + ptr_channel->omemo.enabled = 1; + ptr_channel->pgp.enabled = 0; + + channel__set_transport(ptr_channel, CHANNEL_TRANSPORT_OMEMO, 0); + + return WEECHAT_RC_OK; +} + int command__pgp(const void *pointer, void *data, struct t_gui_buffer *buffer, int argc, char **argv, char **argv_eol) @@ -786,13 +821,48 @@ int command__pgp(const void *pointer, void *data, { keyid = argv_eol[1]; - ptr_channel->pgp_id = strdup(keyid); + ptr_channel->pgp.id = strdup(keyid); } - else + ptr_channel->omemo.enabled = 0; + ptr_channel->pgp.enabled = 1; + + channel__set_transport(ptr_channel, CHANNEL_TRANSPORT_PGP, 0); + + return WEECHAT_RC_OK; +} + +int command__plain(const void *pointer, void *data, + struct t_gui_buffer *buffer, int argc, + char **argv, char **argv_eol) +{ + struct t_account *ptr_account = NULL; + struct t_channel *ptr_channel = NULL; + + (void) pointer; + (void) data; + (void) argc; + (void) argv; + (void) argv_eol; + + buffer__get_account_and_channel(buffer, &ptr_account, &ptr_channel); + + if (!ptr_account) + return WEECHAT_RC_ERROR; + + if (!ptr_channel) { - ptr_channel->pgp_id = NULL; + weechat_printf( + ptr_account->buffer, + _("%s%s: \"%s\" command can not be executed on a account buffer"), + weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME, "plain"); + return WEECHAT_RC_OK; } + ptr_channel->omemo.enabled = 0; + ptr_channel->pgp.enabled = 0; + + channel__set_transport(ptr_channel, CHANNEL_TRANSPORT_PLAIN, 0); + return WEECHAT_RC_OK; } @@ -810,6 +880,9 @@ int command__xml(const void *pointer, void *data, buffer__get_account_and_channel(buffer, &ptr_account, &ptr_channel); + if (!ptr_account) + return WEECHAT_RC_ERROR; + if (!ptr_account->is_connected) { weechat_printf(buffer, @@ -821,7 +894,7 @@ int command__xml(const void *pointer, void *data, if (argc > 1) { stanza = xmpp_stanza_new_from_string(ptr_account->context, - argv_eol[0]); + argv_eol[1]); if (!stanza) return WEECHAT_RC_ERROR; @@ -906,15 +979,33 @@ void command__init() if (!hook) weechat_printf(NULL, "Failed to setup command /mam"); + hook = weechat_hook_command( + "omemo", + N_("set the current buffer to use omemo encryption"), + N_(""), + N_(""), + NULL, &command__omemo, NULL, NULL); + if (!hook) + weechat_printf(NULL, "Failed to setup command /omemo"); + hook = weechat_hook_command( "pgp", - N_("set the target pgp key for the current channel"), + N_("set the current buffer to use pgp encryption (with a given target pgp key)"), N_(""), N_("keyid: recipient keyid"), NULL, &command__pgp, NULL, NULL); if (!hook) weechat_printf(NULL, "Failed to setup command /pgp"); + hook = weechat_hook_command( + "plain", + N_("set the current buffer to use no encryption"), + N_(""), + N_(""), + NULL, &command__plain, NULL, NULL); + if (!hook) + weechat_printf(NULL, "Failed to setup command /plain"); + hook = weechat_hook_command( "xml", N_("send a raw xml stanza"), diff --git a/completion.c b/completion.c index 00e14e5..33875f8 100644 --- a/completion.c +++ b/completion.c @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include #include diff --git a/config.c b/config.c index b67c457..4302d4a 100644 --- a/config.c +++ b/config.c @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include diff --git a/connection.c b/connection.c index 69df89a..92b1b84 100644 --- a/connection.c +++ b/connection.c @@ -24,6 +24,7 @@ void connection__init() { + srand(time(NULL)); xmpp_initialize(); } @@ -85,11 +86,12 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void struct t_account *account = (struct t_account *)userdata; struct t_user *user; struct t_channel *channel; - xmpp_stanza_t *iq__x_signed, *iq__x_muc_user, *show, *idle, *iq__x__item, *iq__c, *iq__status; - const char *from, *from_bare, *from_res, *type, *role = NULL, *affiliation = NULL, *jid = NULL; + xmpp_stanza_t *pres__x_signed, *pres__x_muc_user, *show, *idle, *pres__x__item, *pres__c, *pres__status; + const char *to, *from, *from_bare, *from_res, *type, *role = NULL, *affiliation = NULL, *jid = NULL; const char *show__text = NULL, *idle__since = NULL, *certificate = NULL, *node = NULL, *ver = NULL; char *clientid = NULL, *status; + to = xmpp_stanza_get_to(stanza); from = xmpp_stanza_get_from(stanza); if (from == NULL) return 1; @@ -100,44 +102,107 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void show__text = show ? xmpp_stanza_get_text(show) : NULL; idle = xmpp_stanza_get_child_by_name_and_ns(stanza, "idle", "urn:xmpp:idle:1"); idle__since = idle ? xmpp_stanza_get_attribute(idle, "since") : NULL; - iq__x_signed = xmpp_stanza_get_child_by_name_and_ns( + pres__x_signed = xmpp_stanza_get_child_by_name_and_ns( stanza, "x", "jabber:x:signed"); - if (iq__x_signed) + if (pres__x_signed) { - certificate = xmpp_stanza_get_text(iq__x_signed); + certificate = xmpp_stanza_get_text(pres__x_signed); } - iq__c = xmpp_stanza_get_child_by_name_and_ns( + pres__c = xmpp_stanza_get_child_by_name_and_ns( stanza, "c", "http://jabber.org/protocol/caps"); - if (iq__c) + if (pres__c) { - node = xmpp_stanza_get_attribute(iq__c, "node"); - ver = xmpp_stanza_get_attribute(iq__c, "ver"); + node = xmpp_stanza_get_attribute(pres__c, "node"); + ver = xmpp_stanza_get_attribute(pres__c, "ver"); if (node && ver) { - int len = strlen(node)+1+strlen(ver); + int len = strlen(node)+1+strlen(ver) + 1; clientid = malloc(sizeof(char)*len); snprintf(clientid, len, "%s#%s", node, ver); + + xmpp_stanza_t *children[2] = {NULL}; + children[0] = stanza__iq_pubsub_items(account->context, NULL, + "eu.siacs.conversations.axolotl.devicelist"); + children[0] = stanza__iq_pubsub(account->context, NULL, + children, with_noop("http://jabber.org/protocol/pubsub")); + children[0] = stanza__iq(account->context, NULL, children, NULL, + strdup("fetch2"), to ? strdup(to) : NULL, strdup(from_bare), + strdup("get")); + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); } } - iq__status = xmpp_stanza_get_child_by_name(stanza, "status"); - status = iq__status ? xmpp_stanza_get_text(iq__status) : NULL; - iq__x_muc_user = xmpp_stanza_get_child_by_name_and_ns( + pres__status = xmpp_stanza_get_child_by_name(stanza, "status"); + status = pres__status ? xmpp_stanza_get_text(pres__status) : NULL; + pres__x_muc_user = xmpp_stanza_get_child_by_name_and_ns( stanza, "x", "http://jabber.org/protocol/muc#user"); channel = channel__search(account, from_bare); - if (weechat_strcasecmp(type, "unavailable") && !iq__x_muc_user && !channel) + if (weechat_strcasecmp(type, "unavailable") != 0 && !pres__x_muc_user && !channel) channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare); - if (iq__x_muc_user) - for (iq__x__item = xmpp_stanza_get_children(iq__x_muc_user); - iq__x__item; iq__x__item = xmpp_stanza_get_next(iq__x__item)) + if (pres__x_muc_user) + for (pres__x__item = xmpp_stanza_get_children(pres__x_muc_user); + pres__x__item; pres__x__item = xmpp_stanza_get_next(pres__x__item)) { - if (weechat_strcasecmp(xmpp_stanza_get_name(iq__x__item), "item") != 0) + const char *pres__x__item__name = xmpp_stanza_get_name(pres__x__item); + if (weechat_strcasecmp(pres__x__item__name, "item") != 0) + { + if (weechat_strcasecmp(pres__x__item__name, "status") == 0) + { + const char *pres__x__status__code = xmpp_stanza_get_attribute( + pres__x__item, "code"); + switch (strtol(pres__x__status__code, NULL, 10)) + { + case 100: // Non-Anonymous: [message | Entering a room]: Inform user that any occupant is allowed to see the user's full JID + weechat_buffer_set(channel->buffer, "notify", "2"); + break; + case 101: // : [message (out of band) | Affiliation change]: Inform user that his or her affiliation changed while not in the room + break; + case 102: // : [message | Configuration change]: Inform occupants that room now shows unavailable members + break; + case 103: // : [message | Configuration change]: Inform occupants that room now does not show unavailable members + break; + case 104: // : [message | Configuration change]: Inform occupants that a non-privacy-related room configuration change has occurred + break; + case 110: // Self-Presence: [presence | Any room presence]: Inform user that presence refers to one of its own room occupants + break; + case 170: // Logging Active: [message or initial presence | Configuration change]: Inform occupants that room logging is now enabled + break; + case 171: // : [message | Configuration change]: Inform occupants that room logging is now disabled + break; + case 172: // : [message | Configuration change]: Inform occupants that the room is now non-anonymous + break; + case 173: // : [message | Configuration change]: Inform occupants that the room is now semi-anonymous + break; + case 174: // : [message | Configuration change]: Inform occupants that the room is now fully-anonymous + break; + case 201: // : [presence | Entering a room]: Inform user that a new room has been created + break; + case 210: // Nick Modified: [presence | Entering a room]: Inform user that the service has assigned or modified the occupant's roomnick + break; + case 301: // : [presence | Removal from room]: Inform user that he or she has been banned from the room + break; + case 303: // : [presence | Exiting a room]: Inform all occupants of new room nickname + break; + case 307: // : [presence | Removal from room]: Inform user that he or she has been kicked from the room + break; + case 321: // : [presence | Removal from room]: Inform user that he or she is being removed from the room because of an affiliation change + break; + case 322: // : [presence | Removal from room]: Inform user that he or she is being removed from the room because the room has been changed to members-only and the user is not a member + break; + case 332: // : [presence | Removal from room]: Inform user that he or she is being removed from the room because of a system shutdown + break; + default: + break; + } + } continue; + } - role = xmpp_stanza_get_attribute(iq__x__item, "role"); - affiliation = xmpp_stanza_get_attribute(iq__x__item, "affiliation"); - jid = xmpp_stanza_get_attribute(iq__x__item, "jid"); + role = xmpp_stanza_get_attribute(pres__x__item, "role"); + affiliation = xmpp_stanza_get_attribute(pres__x__item, "affiliation"); + jid = xmpp_stanza_get_attribute(pres__x__item, "jid"); user = user__search(account, from); if (!user) @@ -164,7 +229,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void { user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); if (channel->type != CHANNEL_TYPE_MUC) - channel->pgp_id = user->profile.pgp_id; + channel->pgp.id = user->profile.pgp_id; } if (channel) @@ -202,7 +267,7 @@ int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void { user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); if (channel->type != CHANNEL_TYPE_MUC) - channel->pgp_id = user->profile.pgp_id; + channel->pgp.id = user->profile.pgp_id; } if (channel) @@ -225,8 +290,8 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * (void) conn; struct t_account *account = (struct t_account *)userdata; - struct t_channel *channel; - xmpp_stanza_t *x, *body, *delay, *topic, *replace, *request, *markable, *composing, *sent, *received, *result, *forwarded; + struct t_channel *channel, *parent_channel; + xmpp_stanza_t *x, *body, *delay, *topic, *replace, *request, *markable, *composing, *sent, *received, *result, *forwarded, *event, *items, *item, *list, *device, *encrypted, **children; const char *type, *from, *nick, *from_bare, *to, *to_bare, *id, *thread, *replace_id, *timestamp; char *text, *intext, *difftext = NULL, *cleartext = NULL; struct tm time = {0}; @@ -239,6 +304,9 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * if (topic != NULL) { intext = xmpp_stanza_get_text(topic); + type = xmpp_stanza_get_type(stanza); + if (type != NULL && strcmp(type, "error") == 0) + return 1; from = xmpp_stanza_get_from(stanza); if (from == NULL) return 1; @@ -246,7 +314,12 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * from = xmpp_jid_resource(account->context, from); channel = channel__search(account, from_bare); if (!channel) - channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare); + { + if (weechat_strcasecmp(type, "groupchat") == 0) + channel = channel__new(account, CHANNEL_TYPE_MUC, from_bare, from_bare); + else + channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare); + } channel__update_topic(channel, intext ? intext : "", from, 0); if (intext != NULL) xmpp_free(account->context, intext); @@ -314,6 +387,71 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * } } + event = xmpp_stanza_get_child_by_name_and_ns( + stanza, "event", "http://jabber.org/protocol/pubsub#event"); + if (event) + { + items = xmpp_stanza_get_child_by_name(event, "items"); + if (items) + { + const char *items_node = xmpp_stanza_get_attribute(items, "node"); + from = xmpp_stanza_get_from(stanza); + to = xmpp_stanza_get_to(stanza); + if (items_node + && weechat_strcasecmp(items_node, + "eu.siacs.conversations.axolotl.devicelist") == 0) + { + item = xmpp_stanza_get_child_by_name(items, "item"); + if (item) + { + list = xmpp_stanza_get_child_by_name_and_ns( + item, "list", "eu.siacs.conversations.axolotl"); + if (list) + { + if (account->omemo) + { + omemo__handle_devicelist(account->omemo, + from ? from : account_jid(account), items); + } + + children = malloc(sizeof(*children) * (3 + 1)); + + for (device = xmpp_stanza_get_children(list); + device; device = xmpp_stanza_get_next(device)) + { + const char *name = xmpp_stanza_get_name(device); + if (weechat_strcasecmp(name, "device") != 0) + continue; + + const char *device_id = xmpp_stanza_get_id(device); + + char bundle_node[128] = {0}; + snprintf(bundle_node, sizeof(bundle_node), + "eu.siacs.conversations.axolotl.bundles:%s", + device_id); + + children[1] = NULL; + children[0] = + stanza__iq_pubsub_items(account->context, NULL, + strdup(bundle_node)); + children[0] = + stanza__iq_pubsub(account->context, NULL, children, + with_noop("http://jabber.org/protocol/pubsub")); + char *uuid = xmpp_uuid_gen(account->context); + children[0] = + stanza__iq(account->context, NULL, children, NULL, uuid, + strdup(to), strdup(from), strdup("get")); + xmpp_free(account->context, uuid); + + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); + } + } + } + } + } + } + return 1; } type = xmpp_stanza_get_type(stanza); @@ -339,12 +477,19 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * const char *channel_id = weechat_strcasecmp(account_jid(account), from_bare) == 0 ? to_bare : from_bare; - channel = channel__search(account, channel_id); + parent_channel = channel__search(account, channel_id); + const char *pm_id = weechat_strcasecmp(account_jid(account), from_bare) + == 0 ? to : from; + channel = parent_channel; if (!channel) channel = channel__new(account, weechat_strcasecmp(type, "groupchat") == 0 ? CHANNEL_TYPE_MUC : CHANNEL_TYPE_PM, channel_id, channel_id); + if (channel && channel->type == CHANNEL_TYPE_MUC + && weechat_strcasecmp(type, "chat") == 0) + channel = channel__new(account, CHANNEL_TYPE_PM, + pm_id, pm_id); if (id && (markable || request)) { @@ -399,6 +544,12 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * weechat_list_add(channel->unreads, unread->id, WEECHAT_LIST_POS_END, unread); } + encrypted = xmpp_stanza_get_child_by_name_and_ns(stanza, "encrypted", + "eu.siacs.conversations.axolotl"); + if (encrypted && account->omemo) + { + cleartext = omemo__decode(account, from_bare, encrypted); + } x = xmpp_stanza_get_child_by_name_and_ns(stanza, "x", "jabber:x:encrypted"); intext = xmpp_stanza_get_text(body); if (x) @@ -539,6 +690,12 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * ? xmpp_jid_resource(account->context, from) : from; } + else if (parent_channel && parent_channel->type == CHANNEL_TYPE_MUC) + { + nick = weechat_strcasecmp(channel->name, from) == 0 + ? xmpp_jid_resource(account->context, from) + : xmpp_jid_bare(account->context, from); + } delay = xmpp_stanza_get_child_by_name_and_ns(stanza, "delay", "urn:xmpp:delay"); timestamp = delay ? xmpp_stanza_get_attribute(delay, "stamp") : NULL; if (timestamp) @@ -574,13 +731,13 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * weechat_string_dyn_concat(dyn_tags, replace_id, -1); } - if (date != 0) + if (date != 0 || encrypted) weechat_string_dyn_concat(dyn_tags, ",notify_none", -1); else if (channel->type == CHANNEL_TYPE_PM && weechat_strcasecmp(from_bare, account_jid(account)) != 0) weechat_string_dyn_concat(dyn_tags, ",notify_private", -1); else - weechat_string_dyn_concat(dyn_tags, ",log1", -1); + weechat_string_dyn_concat(dyn_tags, ",notify_message,log1", -1); const char *edit = replace ? "* " : ""; // Losing which message was edited, sadly if (x && text == cleartext && channel->transport != CHANNEL_TRANSPORT_PGP) @@ -590,9 +747,9 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * weechat_prefix("network"), weechat_color("gray"), channel__transport_name(channel->transport)); } - else if (!x && text == intext && channel->transport != CHANNEL_TRANSPORT_PLAINTEXT) + else if (!x && text == intext && channel->transport != CHANNEL_TRANSPORT_PLAIN) { - channel->transport = CHANNEL_TRANSPORT_PLAINTEXT; + channel->transport = CHANNEL_TRANSPORT_PLAIN; weechat_printf_date_tags(channel->buffer, date, NULL, "%s%sTransport: %s", weechat_prefix("network"), weechat_color("gray"), channel__transport_name(channel->transport)); @@ -622,238 +779,204 @@ int connection__message_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * return 1; } -int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) +xmpp_stanza_t *connection__get_caps(xmpp_stanza_t *reply, struct t_account *account, char **hash) { - struct t_account *account = (struct t_account *)userdata; - xmpp_stanza_t *reply, *query, *identity, *feature, *x, *field, *value, *text, *fin; - xmpp_stanza_t *pubsub, *items, *item, *list, *device, **children; - xmpp_stanza_t *storage, *conference, *nick; - static struct utsname osinfo; - - const char *id = xmpp_stanza_get_id(stanza); - query = xmpp_stanza_get_child_by_name_and_ns( - stanza, "query", "http://jabber.org/protocol/disco#info"); - const char *type = xmpp_stanza_get_attribute(stanza, "type"); - if (query && type && weechat_strcasecmp(type, "get") == 0) - { - char *client_name; - - reply = xmpp_stanza_reply(stanza); - xmpp_stanza_set_type(reply, "result"); - - client_name = weechat_string_eval_expression("weechat ${info:version}", - NULL, NULL, NULL); - - identity = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(identity, "identity"); - xmpp_stanza_set_attribute(identity, "category", "client"); - xmpp_stanza_set_attribute(identity, "name", client_name); - xmpp_stanza_set_attribute(identity, "type", "pc"); - xmpp_stanza_add_child(query, identity); - xmpp_stanza_release(identity); - -#define FEATURE(ns) \ - feature = xmpp_stanza_new(account->context); \ - xmpp_stanza_set_name(feature, "feature"); \ - xmpp_stanza_set_attribute(feature, "var", ns); \ - xmpp_stanza_add_child(query, feature); \ - xmpp_stanza_release(feature); - - FEATURE("eu.siacs.conversations.axolotl.devicelist+notify"); - FEATURE("http://jabber.org/protocol/caps"); - FEATURE("http://jabber.org/protocol/chatstates"); - FEATURE("http://jabber.org/protocol/disco#info"); - FEATURE("http://jabber.org/protocol/disco#items"); - FEATURE("http://jabber.org/protocol/muc"); - FEATURE("http://jabber.org/protocol/nick+notify"); - FEATURE("jabber:iq:version"); - FEATURE("jabber:x:conference"); - FEATURE("jabber:x:oob"); - FEATURE("storage:bookmarks+notify"); - FEATURE("urn:xmpp:avatar:metadata+notify"); - FEATURE("urn:xmpp:chat-markers:0"); - FEATURE("urn:xmpp:idle:1"); - //FEATURE("urn:xmpp:jingle-message:0"); - //FEATURE("urn:xmpp:jingle:1"); - //FEATURE("urn:xmpp:jingle:apps:dtls:0"); - //FEATURE("urn:xmpp:jingle:apps:file-transfer:3"); - //FEATURE("urn:xmpp:jingle:apps:file-transfer:4"); - //FEATURE("urn:xmpp:jingle:apps:file-transfer:5"); - //FEATURE("urn:xmpp:jingle:apps:rtp:1"); - //FEATURE("urn:xmpp:jingle:apps:rtp:audio"); - //FEATURE("urn:xmpp:jingle:apps:rtp:video"); - //FEATURE("urn:xmpp:jingle:jet-omemo:0"); - //FEATURE("urn:xmpp:jingle:jet:0"); - //FEATURE("urn:xmpp:jingle:transports:ibb:1"); - //FEATURE("urn:xmpp:jingle:transports:ice-udp:1"); - //FEATURE("urn:xmpp:jingle:transports:s5b:1"); - FEATURE("urn:xmpp:message-correct:0"); - FEATURE("urn:xmpp:ping"); - FEATURE("urn:xmpp:receipts"); - FEATURE("urn:xmpp:time"); + xmpp_stanza_t *query = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(query, "query"); + xmpp_stanza_set_ns(query, "http://jabber.org/protocol/disco#info"); + + char *client_name = weechat_string_eval_expression( + "weechat ${info:version}", NULL, NULL, NULL); + char **serial = weechat_string_dyn_alloc(256); + weechat_string_dyn_concat(serial, "client/pc//", -1); + weechat_string_dyn_concat(serial, client_name, -1); + weechat_string_dyn_concat(serial, "<", -1); + + xmpp_stanza_t *identity = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(identity, "identity"); + xmpp_stanza_set_attribute(identity, "category", "client"); + xmpp_stanza_set_attribute(identity, "name", client_name); + free(client_name); + xmpp_stanza_set_attribute(identity, "type", "pc"); + xmpp_stanza_add_child(query, identity); + xmpp_stanza_release(identity); + + xmpp_stanza_t *feature = NULL; + +#define FEATURE(NS) \ + feature = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(feature, "feature"); \ + xmpp_stanza_set_attribute(feature, "var", NS); \ + xmpp_stanza_add_child(query, feature); \ + xmpp_stanza_release(feature); \ + weechat_string_dyn_concat(serial, NS, -1); \ + weechat_string_dyn_concat(serial, "<", -1); + + FEATURE("eu.siacs.conversations.axolotl.devicelist+notify"); + FEATURE("http://jabber.org/protocol/caps"); + FEATURE("http://jabber.org/protocol/chatstates"); + FEATURE("http://jabber.org/protocol/disco#info"); + FEATURE("http://jabber.org/protocol/disco#items"); + FEATURE("http://jabber.org/protocol/muc"); + FEATURE("http://jabber.org/protocol/nick+notify"); + FEATURE("jabber:iq:version"); + FEATURE("jabber:x:conference"); + FEATURE("jabber:x:oob"); + FEATURE("storage:bookmarks+notify"); + FEATURE("urn:xmpp:avatar:metadata+notify"); + FEATURE("urn:xmpp:chat-markers:0"); + FEATURE("urn:xmpp:idle:1"); + //FEATURE("urn:xmpp:jingle-message:0"); + //FEATURE("urn:xmpp:jingle:1"); + //FEATURE("urn:xmpp:jingle:apps:dtls:0"); + //FEATURE("urn:xmpp:jingle:apps:file-transfer:3"); + //FEATURE("urn:xmpp:jingle:apps:file-transfer:4"); + //FEATURE("urn:xmpp:jingle:apps:file-transfer:5"); + //FEATURE("urn:xmpp:jingle:apps:rtp:1"); + //FEATURE("urn:xmpp:jingle:apps:rtp:audio"); + //FEATURE("urn:xmpp:jingle:apps:rtp:video"); + //FEATURE("urn:xmpp:jingle:jet-omemo:0"); + //FEATURE("urn:xmpp:jingle:jet:0"); + //FEATURE("urn:xmpp:jingle:transports:ibb:1"); + //FEATURE("urn:xmpp:jingle:transports:ice-udp:1"); + //FEATURE("urn:xmpp:jingle:transports:s5b:1"); + FEATURE("urn:xmpp:message-correct:0"); + FEATURE("urn:xmpp:ping"); + FEATURE("urn:xmpp:receipts"); + FEATURE("urn:xmpp:time"); #undef FEATURE - x = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(x, "x"); - xmpp_stanza_set_ns(x, "jabber:x:data"); - xmpp_stanza_set_attribute(x, "type", "result"); - - if (uname(&osinfo) < 0) - { - *osinfo.sysname = 0; - *osinfo.release = 0; - } - - // This is utter bullshit, TODO: anything but this. - { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "FORM_TYPE"); - xmpp_stanza_set_attribute(field, "type", "hidden"); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, "urn:xmpp:dataforms:softwareinfo"); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); - - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); + xmpp_stanza_t *x = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(x, "x"); + xmpp_stanza_set_ns(x, "jabber:x:data"); + xmpp_stanza_set_attribute(x, "type", "result"); - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); - } - - { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "ip_version"); - xmpp_stanza_set_attribute(field, "type", "text-multi"); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, "ipv4"); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); - - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, "ipv6"); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); - - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); - - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); - } - - { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "os"); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, osinfo.sysname); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); - - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); + static struct utsname osinfo; + if (uname(&osinfo) < 0) + { + *osinfo.sysname = 0; + *osinfo.release = 0; + } - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); - } + xmpp_stanza_t *field, *value, *text; + // This is utter bullshit, TODO: anything but this. +#define FEATURE1(VAR, TYPE, VALUE) \ + field = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(field, "field"); \ + xmpp_stanza_set_attribute(field, "var", VAR); \ + if(TYPE) xmpp_stanza_set_attribute(field, "type", TYPE); \ + value = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(value, "value"); \ + text = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_text(text, VALUE); \ + xmpp_stanza_add_child(value, text); \ + xmpp_stanza_release(text); \ + xmpp_stanza_add_child(field, value); \ + xmpp_stanza_release(value); \ + xmpp_stanza_add_child(x, field); \ + xmpp_stanza_release(field); \ + if (strcmp(VAR, "FORM_TYPE") == 0) { \ + weechat_string_dyn_concat(serial, VAR, -1); \ + weechat_string_dyn_concat(serial, "<", -1); \ + } \ + weechat_string_dyn_concat(serial, VALUE, -1); \ + weechat_string_dyn_concat(serial, "<", -1); +#define FEATURE2(VAR, TYPE, VALUE1, VALUE2) \ + field = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(field, "field"); \ + xmpp_stanza_set_attribute(field, "var", VAR); \ + xmpp_stanza_set_attribute(field, "type", TYPE); \ + value = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(value, "value"); \ + text = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_text(text, VALUE1); \ + xmpp_stanza_add_child(value, text); \ + xmpp_stanza_release(text); \ + xmpp_stanza_add_child(field, value); \ + xmpp_stanza_release(value); \ + value = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_name(value, "value"); \ + text = xmpp_stanza_new(account->context); \ + xmpp_stanza_set_text(text, VALUE2); \ + xmpp_stanza_add_child(value, text); \ + xmpp_stanza_release(text); \ + xmpp_stanza_add_child(field, value); \ + xmpp_stanza_release(value); \ + xmpp_stanza_add_child(x, field); \ + xmpp_stanza_release(field); \ + weechat_string_dyn_concat(serial, VAR, -1); \ + weechat_string_dyn_concat(serial, "<", -1); \ + weechat_string_dyn_concat(serial, VALUE1, -1); \ + weechat_string_dyn_concat(serial, "<", -1); \ + weechat_string_dyn_concat(serial, VALUE2, -1); \ + weechat_string_dyn_concat(serial, "<", -1); + + FEATURE1("FORM_TYPE", "hidden", "urn:xmpp:dataforms:softwareinfo"); + FEATURE2("ip_version", "text-multi", "ipv4", "ipv6"); + FEATURE1("os", NULL, osinfo.sysname); + FEATURE1("os_version", NULL, osinfo.release); + FEATURE1("software", NULL, "weechat"); + FEATURE1("software_version", NULL, weechat_info_get("version", NULL)); +#undef FEATURE1 +#undef FEATURE2 + + xmpp_stanza_add_child(query, x); + xmpp_stanza_release(x); - { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "os_version"); + xmpp_stanza_set_type(reply, "result"); + xmpp_stanza_add_child(reply, query); - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); + unsigned char digest[20]; + xmpp_sha1_t *sha1 = xmpp_sha1_new(account->context); + xmpp_sha1_update(sha1, (unsigned char*)*serial, strlen(*serial)); + xmpp_sha1_final(sha1); + weechat_string_dyn_free(serial, 1); + xmpp_sha1_to_digest(sha1, digest); + xmpp_sha1_free(sha1); - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, osinfo.release); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); + if (hash) + { + char *cap_hash = xmpp_base64_encode(account->context, digest, 20); + *hash = strdup(cap_hash); + xmpp_free(account->context, cap_hash); + } - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); + return reply; +} - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); - } +int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) +{ + struct t_account *account = (struct t_account *)userdata; + xmpp_stanza_t *reply, *query, *text, *fin; + xmpp_stanza_t *pubsub, *items, *item, *list, *bundle, *device; + xmpp_stanza_t *storage, *conference, *nick; + const char *id = xmpp_stanza_get_id(stanza); + const char *from = xmpp_stanza_get_from(stanza); + const char *to = xmpp_stanza_get_to(stanza); + query = xmpp_stanza_get_child_by_name_and_ns( + stanza, "query", "http://jabber.org/protocol/disco#info"); + const char *type = xmpp_stanza_get_attribute(stanza, "type"); + if (query && type) + { + if (weechat_strcasecmp(type, "get") == 0) { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "software"); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, "weechat"); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); + reply = connection__get_caps(xmpp_stanza_reply(stanza), account, NULL); - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); - - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); + xmpp_send(conn, reply); + xmpp_stanza_release(reply); } + if (weechat_strcasecmp(type, "result") == 0) { - field = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(field, "field"); - xmpp_stanza_set_attribute(field, "var", "software_version"); - - value = xmpp_stanza_new(account->context); - xmpp_stanza_set_name(value, "value"); - - text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(text, weechat_info_get("version", NULL)); - xmpp_stanza_add_child(value, text); - xmpp_stanza_release(text); - - xmpp_stanza_add_child(field, value); - xmpp_stanza_release(value); - - xmpp_stanza_add_child(x, field); - xmpp_stanza_release(field); } - - xmpp_stanza_add_child(query, x); - xmpp_stanza_release(x); - - xmpp_stanza_add_child(reply, query); - - xmpp_send(conn, reply); - xmpp_stanza_release(reply); - - free(client_name); } pubsub = xmpp_stanza_get_child_by_name_and_ns( stanza, "pubsub", "http://jabber.org/protocol/pubsub"); if (pubsub) { - const char *items_node, *item_id, *device_id, *ns, *node; + const char *items_node, *item_id, *device_id; items = xmpp_stanza_get_child_by_name(pubsub, "items"); if (items) @@ -865,33 +988,16 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd { item = xmpp_stanza_get_child_by_name(items, "item"); if (item) - item_id = xmpp_stanza_get_id(item); - if (item && item_id && weechat_strcasecmp(item_id, "current") == 0) { + item_id = xmpp_stanza_get_id(item); list = xmpp_stanza_get_child_by_name_and_ns( item, "list", "eu.siacs.conversations.axolotl"); if (list && account->omemo) { - account__free_device_all(account); - - struct t_account_device *dev; - char id[64] = {0}; - int i = 0; - - dev = malloc(sizeof(struct t_account_device)); - - dev->id = account->omemo->device_id; - snprintf(id, sizeof(id), "%d", dev->id); - dev->name = strdup(id); - account__add_device(account, dev); - - children = malloc(sizeof(xmpp_stanza_t *) * 128); - children[i++] = stanza__iq_pubsub_publish_item_list_device( - account->context, NULL, with_noop(dev->name)); - - free(dev->name); - free(dev); + omemo__handle_devicelist(account->omemo, + from ? from : account_jid(account), items); + xmpp_stanza_t *children[2] = {NULL}; for (device = xmpp_stanza_get_children(list); device; device = xmpp_stanza_get_next(device)) { @@ -899,95 +1005,104 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd if (weechat_strcasecmp(name, "device") != 0) continue; - device_id = xmpp_stanza_get_id(device); + const char *device_id = xmpp_stanza_get_id(device); + + char bundle_node[128] = {0}; + snprintf(bundle_node, sizeof(bundle_node), + "eu.siacs.conversations.axolotl.bundles:%s", + device_id); + + children[1] = NULL; + children[0] = + stanza__iq_pubsub_items(account->context, NULL, + strdup(bundle_node)); + children[0] = + stanza__iq_pubsub(account->context, NULL, children, + with_noop("http://jabber.org/protocol/pubsub")); + char *uuid = xmpp_uuid_gen(account->context); + children[0] = + stanza__iq(account->context, NULL, children, NULL, uuid, + to ? strdup(to) : NULL, from ? strdup(from) : NULL, + strdup("get")); + xmpp_free(account->context, uuid); + + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); + } + + if (weechat_strcasecmp(account_jid(account), from) == 0) + { + struct t_account_device *dev; + char id[64] = {0}; + + account__free_device_all(account); dev = malloc(sizeof(struct t_account_device)); - dev->id = atoi(device_id); - dev->name = strdup(device_id); - account__add_device(account, dev); - children[i++] = stanza__iq_pubsub_publish_item_list_device( - account->context, NULL, with_noop(dev->name)); + dev->id = account->omemo->device_id; + snprintf(id, sizeof(id), "%d", dev->id); + dev->name = strdup(id); + dev->label = strdup("weechat"); + account__add_device(account, dev); + free(dev->label); free(dev->name); free(dev); - } - children[i] = NULL; - node = "eu.siacs.conversations.axolotl"; - children[0] = stanza__iq_pubsub_publish_item_list( - account->context, NULL, children, with_noop(node)); - children[1] = NULL; - children[0] = stanza__iq_pubsub_publish_item( - account->context, NULL, children, with_noop("current")); - ns = "http://jabber.org/protocol/pubsub"; - children[0] = stanza__iq_pubsub_publish(account->context, - NULL, children, - with_noop(ns)); - children[0] = stanza__iq_pubsub(account->context, NULL, - children, with_noop("")); - reply = stanza__iq(account->context, xmpp_stanza_reply(stanza), - children, NULL, strdup("announce1"), - NULL, NULL, strdup("set")); - - xmpp_send(conn, reply); - xmpp_stanza_release(reply); - - char bundle_node[128] = {0}; - snprintf(bundle_node, sizeof(bundle_node), - "eu.siacs.conversations.axolotl.bundles:%d", - account->omemo->device_id); - - xmpp_stanza_t *textchild[2] = {NULL}; - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc1"); - children[0] = stanza__iq_pubsub_publish_item_bundle_signedPreKeyPublic( - account->context, NULL, textchild, with_noop("1")); - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc2"); - children[1] = stanza__iq_pubsub_publish_item_bundle_signedPreKeySignature( - account->context, NULL, textchild); - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc3"); - children[2] = stanza__iq_pubsub_publish_item_bundle_identityKey( - account->context, NULL, textchild); - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc4"); - children[3] = stanza__iq_pubsub_publish_item_bundle_prekeys_preKeyPublic( - account->context, NULL, textchild, with_noop("1")); - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc5"); - children[4] = stanza__iq_pubsub_publish_item_bundle_prekeys_preKeyPublic( - account->context, NULL, textchild, with_noop("2")); - textchild[0] = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(textchild[0], "b64enc6"); - children[5] = stanza__iq_pubsub_publish_item_bundle_prekeys_preKeyPublic( - account->context, NULL, textchild, with_noop("3")); - children[6] = NULL; - children[3] = stanza__iq_pubsub_publish_item_bundle_prekeys( - account->context, NULL, &children[3]); - children[4] = NULL; - ns = "eu.siacs.conversations.axolotl"; - children[0] = stanza__iq_pubsub_publish_item_bundle( - account->context, NULL, children, with_noop(ns)); - children[1] = NULL; - children[0] = stanza__iq_pubsub_publish_item( - account->context, NULL, children, with_noop("current")); - children[0] = stanza__iq_pubsub_publish(account->context, - NULL, children, - with_noop(bundle_node)); - children[0] = - stanza__iq_pubsub(account->context, NULL, children, - with_noop("http://jabber.org/protocol/pubsub")); - children[0] = - stanza__iq(account->context, NULL, children, NULL, strdup("announce2"), - strdup(account_jid(account)), strdup(account_jid(account)), - strdup("set")); + for (device = xmpp_stanza_get_children(list); + device; device = xmpp_stanza_get_next(device)) + { + const char *name = xmpp_stanza_get_name(device); + if (weechat_strcasecmp(name, "device") != 0) + continue; - xmpp_send(conn, children[0]); - xmpp_stanza_release(children[0]); + device_id = xmpp_stanza_get_id(device); - free(children); + dev = malloc(sizeof(struct t_account_device)); + dev->id = atoi(device_id); + dev->name = strdup(device_id); + dev->label = NULL; + account__add_device(account, dev); + + free(dev->label); + free(dev->name); + free(dev); + } + + reply = account__get_devicelist(account); + char *uuid = xmpp_uuid_gen(account->context); + xmpp_stanza_set_id(reply, uuid); + xmpp_free(account->context, uuid); + xmpp_stanza_set_attribute(reply, "to", from); + xmpp_stanza_set_attribute(reply, "from", to); + xmpp_send(conn, reply); + xmpp_stanza_release(reply); + } + } + } + } + if (items_node + && strncmp(items_node, + "eu.siacs.conversations.axolotl.bundles", + strnlen(items_node, + strlen("eu.siacs.conversations.axolotl.bundles"))) == 0) + { + item = xmpp_stanza_get_child_by_name(items, "item"); + if (item) + { + bundle = xmpp_stanza_get_child_by_name_and_ns(item, "bundle", "eu.siacs.conversations.axolotl"); + if (bundle) + { + size_t node_prefix = + strlen("eu.siacs.conversations.axolotl.bundles:"); + if (account->omemo && strlen(items_node) > node_prefix) + { + omemo__handle_bundle(account->omemo, + from ? from : account_jid(account), + strtol(items_node+node_prefix, + NULL, 10), + items); + } } } } @@ -1032,6 +1147,12 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd weechat_string_dyn_concat(command, intext, -1); } weechat_command(account->buffer, *command); + struct t_channel *ptr_channel = + channel__search(account, jid); + struct t_gui_buffer *ptr_buffer = + ptr_channel ? ptr_channel->buffer : NULL; + if (ptr_buffer) + weechat_buffer_set(ptr_buffer, "short_name", name); weechat_string_dyn_free(command, 1); } @@ -1094,7 +1215,6 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, xmpp_stanza_t *pres, *pres__c, *pres__status, *pres__status__text, *pres__x, *pres__x__text, **children; - char cap_hash[28+1] = {0}; xmpp_handler_add(conn, &connection__version_handler, "jabber:iq:version", "iq", NULL, account); @@ -1121,8 +1241,15 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, xmpp_stanza_set_ns(pres__c, "http://jabber.org/protocol/caps"); xmpp_stanza_set_attribute(pres__c, "hash", "sha-1"); xmpp_stanza_set_attribute(pres__c, "node", "http://weechat.org"); - snprintf(cap_hash, sizeof(cap_hash), "%027ld=", time(NULL)); + + xmpp_stanza_t *caps = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(caps, "caps"); + char *cap_hash; + caps = connection__get_caps(caps, account, &cap_hash); + xmpp_stanza_release(caps); xmpp_stanza_set_attribute(pres__c, "ver", cap_hash); + free(cap_hash); + children[0] = pres__c; pres__status = xmpp_stanza_new(account->context); @@ -1192,16 +1319,27 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, children[0] = stanza__iq_pubsub(account->context, NULL, children, with_noop("http://jabber.org/protocol/pubsub")); + char *uuid = xmpp_uuid_gen(account->context); children[0] = - stanza__iq(account->context, NULL, children, NULL, strdup("fetch1"), + stanza__iq(account->context, NULL, children, NULL, uuid, strdup(account_jid(account)), strdup(account_jid(account)), strdup("get")); + xmpp_free(account->context, uuid); xmpp_send(conn, children[0]); xmpp_stanza_release(children[0]); omemo__init(account->buffer, &account->omemo, account->name); + if (account->omemo) + { + children[0] = + omemo__get_bundle(account->context, + strdup(account_jid(account)), NULL, account->omemo); + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); + } + (void) weechat_hook_signal_send("xmpp_account_connected", WEECHAT_HOOK_SIGNAL_STRING, account->name); } @@ -1215,7 +1353,6 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, char* connection__rand_string(int length) { char *string = malloc(length); - srand(time(NULL)); for(int i = 0; i < length; ++i){ string[i] = '0' + rand()%72; // starting on '0', ending on '}' if (!((string[i] >= '0' && string[i] <= '9') || diff --git a/input.c b/input.c index 3f88f8b..d51b3e4 100644 --- a/input.c +++ b/input.c @@ -4,6 +4,8 @@ #include #include +#include +#include #include "plugin.h" #include "account.h" @@ -32,16 +34,20 @@ int input__data(struct t_gui_buffer *buffer, const char *text) return WEECHAT_RC_OK; } - channel__send_message(account, channel, channel->id, text); + if (channel__send_message(account, channel, channel->id, text) == WEECHAT_RC_OK) + return WEECHAT_RC_OK; + else + { + return WEECHAT_RC_OK_EAT; + } } else { weechat_printf(buffer, _("%s%s: this buffer is not a channel!"), weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME); + return WEECHAT_RC_OK; } - - return WEECHAT_RC_OK; } int input__data_cb(const void *pointer, void *data, diff --git a/libomemo.scm b/libomemo.scm new file mode 100644 index 0000000..b9c7139 --- /dev/null +++ b/libomemo.scm @@ -0,0 +1,126 @@ +(define-module (libomemo) + #:use-module (gnu packages) + #:use-module (gnu packages admin) + #:use-module (gnu packages aidc) + #:use-module (gnu packages aspell) + #:use-module (gnu packages audio) + #:use-module (gnu packages autotools) + #:use-module (gnu packages avahi) + #:use-module (gnu packages base) + #:use-module (gnu packages bash) + #:use-module (gnu packages bison) + #:use-module (gnu packages boost) + #:use-module (gnu packages check) + #:use-module (gnu packages compression) + #:use-module (gnu packages cpp) + #:use-module (gnu packages crypto) + #:use-module (gnu packages curl) + #:use-module (gnu packages cyrus-sasl) + #:use-module (gnu packages databases) + #:use-module (gnu packages docbook) + #:use-module (gnu packages documentation) + #:use-module (gnu packages enchant) + #:use-module (gnu packages fontutils) + #:use-module (gnu packages freedesktop) + #:use-module (gnu packages gettext) + #:use-module (gnu packages glib) + #:use-module (gnu packages gnome) + #:use-module (gnu packages gnupg) + #:use-module (gnu packages golang) + #:use-module (gnu packages gperf) + #:use-module (gnu packages graphviz) + #:use-module (gnu packages gstreamer) + #:use-module (gnu packages gtk) + #:use-module (gnu packages guile) + #:use-module (gnu packages icu4c) + #:use-module (gnu packages image) + #:use-module (gnu packages kde) + #:use-module (gnu packages kerberos) + #:use-module (gnu packages less) + #:use-module (gnu packages libcanberra) + #:use-module (gnu packages libffi) + #:use-module (gnu packages libidn) + #:use-module (gnu packages libreoffice) + #:use-module (gnu packages linux) + #:use-module (gnu packages logging) + #:use-module (gnu packages lua) + #:use-module (gnu packages man) + #:use-module (gnu packages markup) + #:use-module (gnu packages matrix) + #:use-module (gnu packages mono) + #:use-module (gnu packages mpd) + #:use-module (gnu packages ncurses) + #:use-module (gnu packages networking) + #:use-module (gnu packages nss) + #:use-module (gnu packages pcre) + #:use-module (gnu packages perl) + #:use-module (gnu packages photo) + #:use-module (gnu packages php) + #:use-module (gnu packages pkg-config) + #:use-module (gnu packages protobuf) + #:use-module (gnu packages python) + #:use-module (gnu packages python-check) + #:use-module (gnu packages python-crypto) + #:use-module (gnu packages python-web) + #:use-module (gnu packages python-xyz) + #:use-module (gnu packages qt) + #:use-module (gnu packages readline) + #:use-module (gnu packages ruby) + #:use-module (gnu packages sphinx) + #:use-module (gnu packages sqlite) + #:use-module (gnu packages tcl) + #:use-module (gnu packages texinfo) + #:use-module (gnu packages textutils) + #:use-module (gnu packages tls) + #:use-module (gnu packages video) + #:use-module (gnu packages web) + #:use-module (gnu packages xdisorg) + #:use-module (gnu packages xiph) + #:use-module (gnu packages xml) + #:use-module (gnu packages xorg) + #:use-module (guix build-system cmake) + #:use-module (guix build-system go) + #:use-module (guix build-system glib-or-gtk) + #:use-module (guix build-system gnu) + #:use-module (guix build-system meson) + #:use-module (guix build-system perl) + #:use-module (guix build-system python) + #:use-module (guix build-system qt) + #:use-module (guix build-system trivial) + #:use-module (guix download) + #:use-module (guix git-download) + #:use-module (guix hg-download) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix packages) + #:use-module (guix utils)) + +(define-public libomemo-c + (package + (name "libomemo-c") + (version "2.3.3") + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/dino/libomemo-c") + (commit "06184660790daa42433e616fa3dee730717d1c1b"))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "1wa85r1b54myabsbmbqlq9jz174mmvd02b8zy1j4j0kml0anp6nx")))) + (arguments + `(;; Required for proper linking and for tests to run. + #:configure-flags '("-DBUILD_SHARED_LIBS=on" "-DBUILD_TESTING=1"))) + (build-system cmake-build-system) + (inputs `( ;; Required for tests: + ("check" ,check) + ("openssl" ,openssl))) + (native-inputs `(("pkg-config" ,pkg-config))) + (home-page "https://github.com/WhisperSystems/libsignal-protocol-c") + (synopsis "Implementation of a ratcheting forward secrecy protocol") + (description "libsignal-protocol-c is an implementation of a ratcheting +forward secrecy protocol that works in synchronous and asynchronous +messaging environments. It can be used with messaging software to provide +end-to-end encryption.") + (license license:gpl3+))) + +libomemo-c diff --git a/Makefile b/makefile similarity index 97% rename from Makefile rename to makefile index 9ad8223..1d67af8 100644 --- a/Makefile +++ b/makefile @@ -9,7 +9,7 @@ FIND=find INCLUDES=-Ilibstrophe \ $(shell xml2-config --cflags) \ $(shell pkg-config --cflags librnp-0) \ - $(shell pkg-config --cflags libsignal-protocol-c) + $(shell pkg-config --cflags libomemo-c) CFLAGS+=$(DBGCFLAGS) \ -fno-omit-frame-pointer -fPIC \ -std=gnu99 -gdwarf-4 \ @@ -32,7 +32,7 @@ LDLIBS=-lstrophe \ -lpthread \ $(shell xml2-config --libs) \ $(shell pkg-config --libs librnp-0) \ - $(shell pkg-config --libs libsignal-protocol-c) \ + $(shell pkg-config --libs libomemo-c) \ -lgcrypt \ -llmdb diff --git a/message.c b/message.c index 9465b3b..2b0c874 100644 --- a/message.c +++ b/message.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include diff --git a/omemo.c b/omemo.c index 144bf13..d778d47 100644 --- a/omemo.c +++ b/omemo.c @@ -4,11 +4,17 @@ #include #include +#include #include #include +#include #include #include #include +#include +#include +#include +#include #include #include #include @@ -18,9 +24,16 @@ struct t_omemo_db { MDB_dbi dbi_omemo; }; +struct t_pre_key { + const char *id; + const char *public_key; +}; + #include "plugin.h" +#include "xmpp/stanza.h" #include "account.h" #include "omemo.h" +#include "util.h" #define mdb_val_str(s) { \ .mv_data = s, .mv_size = strlen(s), \ @@ -34,8 +47,73 @@ struct t_omemo_db { .mv_data = NULL, .mv_size = sizeof(t), \ } +#define PRE_KEY_START 1 +#define PRE_KEY_COUNT 100 + +#define AES_KEY_SIZE (16) +#define AES_IV_SIZE (12) + const char *OMEMO_ADVICE = "[OMEMO encrypted message (XEP-0384)]"; +size_t base64_decode(const char *buffer, size_t length, uint8_t **result) +{ + *result = calloc(length + 1, sizeof(uint8_t)); + return weechat_string_base_decode(64, buffer, (char*)*result); +} + +size_t base64_encode(const uint8_t *buffer, size_t length, char **result) +{ + *result = calloc(length * 2, sizeof(char)); + return weechat_string_base_encode(64, (char*)buffer, length, *result); +} + +int aes_decrypt(const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t *key, uint8_t *iv, uint8_t *tag, size_t tag_len, + uint8_t **plaintext, size_t *plaintext_len) +{ + gcry_cipher_hd_t cipher = NULL; + if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE)) goto cleanup; + if (gcry_cipher_setkey(cipher, key, AES_KEY_SIZE)) goto cleanup; + if (gcry_cipher_setiv(cipher, iv, AES_IV_SIZE)) goto cleanup; + *plaintext_len = ciphertext_len; + *plaintext = malloc((sizeof(uint8_t) * *plaintext_len) + 1); + if (gcry_cipher_decrypt(cipher, *plaintext, *plaintext_len, + ciphertext, ciphertext_len)) goto cleanup; + if (gcry_cipher_checktag(cipher, tag, tag_len)) goto cleanup; + gcry_cipher_close(cipher); + return 1; +cleanup: + gcry_cipher_close(cipher); + return 0; +} + +int aes_encrypt(const uint8_t *plaintext, size_t plaintext_len, + uint8_t **key, uint8_t **iv, uint8_t **tag, size_t *tag_len, + uint8_t **ciphertext, size_t *ciphertext_len) +{ + *tag_len = 16; + *tag = calloc(*tag_len, sizeof(uint8_t)); + *iv = gcry_random_bytes(AES_IV_SIZE, GCRY_STRONG_RANDOM); + *key = gcry_random_bytes(AES_KEY_SIZE, GCRY_STRONG_RANDOM); + + gcry_cipher_hd_t cipher = NULL; + if (gcry_cipher_open(&cipher, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_GCM, GCRY_CIPHER_SECURE)) goto cleanup; + if (gcry_cipher_setkey(cipher, *key, AES_KEY_SIZE)) goto cleanup; + if (gcry_cipher_setiv(cipher, *iv, AES_IV_SIZE)) goto cleanup; + *ciphertext_len = plaintext_len; + *ciphertext = malloc((sizeof(uint8_t) * *ciphertext_len) + 1); + if (gcry_cipher_encrypt(cipher, *ciphertext, *ciphertext_len, + plaintext, plaintext_len)) goto cleanup; + if (gcry_cipher_gettag(cipher, *tag, *tag_len)) goto cleanup; + gcry_cipher_close(cipher); + return 1; +cleanup: + gcry_cipher_close(cipher); + return 0; +} + void signal_protocol_address_free(signal_protocol_address* ptr) { if (!ptr) return; @@ -407,21 +485,21 @@ no_error: int iks_get_identity_key_pair(signal_buffer **public_data, signal_buffer **private_data, void *user_data) { struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; + MDB_txn *transaction = NULL; MDB_val k_local_private_key = mdb_val_str("local_private_key"); MDB_val k_local_public_key = mdb_val_str("local_public_key"); MDB_val v_local_private_key, v_local_public_key; - // Get the local client's identity key pair if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); + return -1; } - if (mdb_get(transaction, omemo->db->dbi_omemo, - &k_local_private_key, &v_local_private_key) && - mdb_get(transaction, omemo->db->dbi_omemo, - &k_local_public_key, &v_local_public_key)) + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_local_private_key, &v_local_private_key) && + !mdb_get(transaction, omemo->db->dbi_omemo, + &k_local_public_key, &v_local_public_key)) { *private_data = signal_buffer_create(v_local_private_key.mv_data, v_local_private_key.mv_size); *public_data = signal_buffer_create(v_local_public_key.mv_data, v_local_public_key.mv_size); @@ -429,21 +507,13 @@ int iks_get_identity_key_pair(signal_buffer **public_data, signal_buffer **priva if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; } else { struct ratchet_identity_key_pair *identity; - mdb_txn_abort(transaction); - - if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { - weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", - weechat_prefix("error")); - return -1; - } - signal_protocol_key_helper_generate_identity_key_pair( &identity, omemo->context); ec_private_key *private_key = ratchet_identity_key_pair_get_private(identity); @@ -457,30 +527,46 @@ int iks_get_identity_key_pair(signal_buffer **public_data, signal_buffer **priva v_local_public_key.mv_data = signal_buffer_data(*public_data); v_local_public_key.mv_size = signal_buffer_len(*public_data); + mdb_txn_abort(transaction); + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + if (mdb_put(transaction, omemo->db->dbi_omemo, - &k_local_private_key, &v_local_private_key, MDB_NOOVERWRITE) && + &k_local_private_key, &v_local_private_key, MDB_NOOVERWRITE) || mdb_put(transaction, omemo->db->dbi_omemo, &k_local_public_key, &v_local_public_key, MDB_NOOVERWRITE)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb value", weechat_prefix("error")); - return -1; + goto cleanup; }; if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; + + *private_data = signal_buffer_create(v_local_private_key.mv_data, + v_local_private_key.mv_size); + *public_data = signal_buffer_create(v_local_public_key.mv_data, + v_local_public_key.mv_size); + omemo->identity = identity; } return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; } int iks_get_local_registration_id(void *user_data, uint32_t *registration_id) { struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; + MDB_txn *transaction = NULL; MDB_val k_local_registration_id = mdb_val_str("local_registration_id"); MDB_val v_local_registration_id = mdb_val_sizeof(uint32_t); @@ -491,142 +577,129 @@ int iks_get_local_registration_id(void *user_data, uint32_t *registration_id) return -1; } - if (mdb_get(transaction, omemo->db->dbi_omemo, - &k_local_registration_id, - &v_local_registration_id)) + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_local_registration_id, + &v_local_registration_id)) { *registration_id = *(uint32_t*)v_local_registration_id.mv_data; if (mdb_txn_commit(transaction)) { - weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_printf(NULL, "%sxmpp: failed to read lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; } else { - mdb_txn_abort(transaction); + uint32_t generated_id; + signal_protocol_key_helper_generate_registration_id( + &generated_id, 0, omemo->context); + v_local_registration_id.mv_data = &generated_id; + mdb_txn_abort(transaction); if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); return -1; } - signal_protocol_key_helper_generate_registration_id( - (uint32_t*)&v_local_registration_id.mv_data, 0, omemo->context); - if (mdb_put(transaction, omemo->db->dbi_omemo, &k_local_registration_id, &v_local_registration_id, MDB_NOOVERWRITE)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb value", weechat_prefix("error")); - return -1; + goto cleanup; }; if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; + + *registration_id = generated_id; } return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; } int iks_save_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data) { struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; - MDB_val k_registration_id = { - .mv_data = NULL, - .mv_size = strlen("registration_id_") + address->name_len, - }; - MDB_val v_registration_id = mdb_val_intptr((uint32_t*)&address->device_id); + MDB_txn *transaction = NULL; MDB_val k_identity_key = { .mv_data = NULL, - .mv_size = strlen("identity_key_") + address->name_len, + .mv_size = strlen("identity_key_") + address->name_len + + 1 + 10, }; MDB_val v_identity_key = {.mv_data = key_data, .mv_size = key_len}; - k_registration_id.mv_data = malloc(sizeof(char) * ( - k_registration_id.mv_size + 1)); - snprintf(k_registration_id.mv_data, k_registration_id.mv_size, - "registration_id_%s", address->name); k_identity_key.mv_data = malloc(sizeof(char) * ( k_identity_key.mv_size + 1)); - snprintf(k_identity_key.mv_data, k_identity_key.mv_size, - "identity_key_%s", address->name); + k_identity_key.mv_size = + snprintf(k_identity_key.mv_data, k_identity_key.mv_size + 1, + "identity_key_%s_%u", address->name, address->device_id); - // Save a remote client's identity key if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); return -1; } - if (mdb_put(transaction, omemo->db->dbi_omemo, &k_registration_id, - &v_registration_id, 0) || - mdb_put(transaction, omemo->db->dbi_omemo, &k_identity_key, + if (mdb_put(transaction, omemo->db->dbi_omemo, &k_identity_key, &v_identity_key, 0)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb value", weechat_prefix("error")); - return -1; + goto cleanup; }; if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; } int iks_is_trusted_identity(const signal_protocol_address *address, uint8_t *key_data, size_t key_len, void *user_data) { struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; - MDB_val k_registration_id = { - .mv_data = NULL, - .mv_size = strlen("registration_id_") + address->name_len, - }; - MDB_val v_registration_id = mdb_val_intptr((uint32_t*)&address->device_id); + MDB_txn *transaction = NULL; MDB_val k_identity_key = { .mv_data = NULL, - .mv_size = strlen("identity_key_") + address->name_len, + .mv_size = strlen("identity_key_") + address->name_len + + 1 + 10, }; MDB_val v_identity_key = {.mv_data = key_data, .mv_size = key_len}; int trusted = 1; - k_registration_id.mv_data = malloc(sizeof(char) * ( - k_registration_id.mv_size + 1)); - snprintf(k_registration_id.mv_data, k_registration_id.mv_size, - "registration_id_%s", address->name); k_identity_key.mv_data = malloc(sizeof(char) * ( k_identity_key.mv_size + 1)); - snprintf(k_identity_key.mv_data, k_identity_key.mv_size, - "identity_key_%s", address->name); + k_identity_key.mv_size = + snprintf(k_identity_key.mv_data, k_identity_key.mv_size + 1, + "identity_key_%s_%u", address->name, address->device_id); - // Verify a remote client's identity key if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); return -1; } - if (mdb_get(transaction, omemo->db->dbi_omemo, &k_registration_id, - &v_registration_id) || - mdb_get(transaction, omemo->db->dbi_omemo, &k_identity_key, + if (mdb_get(transaction, omemo->db->dbi_omemo, &k_identity_key, &v_identity_key)) { - weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_printf(NULL, "%sxmpp: failed to read lmdb value", weechat_prefix("error")); - return -1; + goto cleanup; }; - if (*(uint32_t*)v_registration_id.mv_data != (uint32_t)address->device_id) - trusted = 0; if (v_identity_key.mv_size != key_len || memcmp(v_identity_key.mv_data, key_data, key_len) != 0) trusted = 0; @@ -634,10 +707,13 @@ int iks_is_trusted_identity(const signal_protocol_address *address, uint8_t *key if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; - return trusted; + return 1 | trusted; +cleanup: + mdb_txn_abort(transaction); + return -1; } void iks_destroy_func(void *user_data) @@ -647,289 +723,1236 @@ void iks_destroy_func(void *user_data) // Function called to perform cleanup when the data store context is being destroyed } -int pks_load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data) +int pks_store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data) +{ + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_pre_key = { + .mv_data = NULL, + .mv_size = strlen("pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_pre_key = {.mv_data = record, .mv_size = record_len}; + + k_pre_key.mv_data = malloc(sizeof(char) * ( + k_pre_key.mv_size + 1)); + k_pre_key.mv_size = + snprintf(k_pre_key.mv_data, k_pre_key.mv_size + 1, + "pre_key_%-10u", pre_key_id); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_put(transaction, omemo->db->dbi_omemo, &k_pre_key, + &v_pre_key, 0)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; +} + +int pks_contains_pre_key(uint32_t pre_key_id, void *user_data) { struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; - MDB_val k_pre_key = mdb_val_str("pre_key"); + MDB_txn *transaction = NULL; + MDB_val k_pre_key = { + .mv_data = NULL, + .mv_size = strlen("pre_key_") + 10, // strlen(UINT32_MAX) + }; MDB_val v_pre_key; + k_pre_key.mv_data = malloc(sizeof(char) * ( + k_pre_key.mv_size + 1)); + k_pre_key.mv_size = + snprintf(k_pre_key.mv_data, k_pre_key.mv_size + 1, + "pre_key_%-10u", pre_key_id); + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); + return -1; } - if (mdb_get(transaction, omemo->db->dbi_omemo, - &k_pre_key, &v_pre_key)) + if (mdb_get(transaction, omemo->db->dbi_omemo, &k_pre_key, + &v_pre_key)) { + weechat_printf(NULL, "%sxmpp: failed to read lmdb value", + weechat_prefix("error")); + mdb_txn_abort(transaction); + goto cleanup; + }; + + mdb_txn_abort(transaction); + + return 1; +cleanup: + mdb_txn_abort(transaction); + return 0; +} + +uint32_t pks_get_count(struct t_omemo *omemo, int increment) +{ + uint32_t count = PRE_KEY_START; + MDB_txn *transaction = NULL; + MDB_val k_pre_key_idx = { + .mv_data = "pre_key_idx", + .mv_size = strlen("pre_key_idx"), + }; + MDB_val v_pre_key_idx = mdb_val_intptr(&count); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_pre_key_idx, &v_pre_key_idx)) + { + if (increment) + count += PRE_KEY_COUNT; + } + + if (mdb_put(transaction, omemo->db->dbi_omemo, + &k_pre_key_idx, &v_pre_key_idx, 0)) + { + weechat_printf(NULL, "%sxmpp: failed to read lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return count; +cleanup: + mdb_txn_abort(transaction); + return 0; +} + +int pks_load_pre_key(signal_buffer **record, uint32_t pre_key_id, void *user_data) +{ + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_pre_key = { + .mv_data = NULL, + .mv_size = strlen("pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_pre_key; + + k_pre_key.mv_data = malloc(sizeof(char) * ( + k_pre_key.mv_size + 1)); + k_pre_key.mv_size = + snprintf(k_pre_key.mv_data, k_pre_key.mv_size + 1, + "pre_key_%-10u", pre_key_id); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_pre_key, &v_pre_key)) { *record = signal_buffer_create(v_pre_key.mv_data, v_pre_key.mv_size); if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; } else { - signal_protocol_key_helper_pre_key_list_node *pre_keys_list; - session_pre_key *pre_key = NULL; - mdb_txn_abort(transaction); - /* - signal_protocol_key_helper_generate_pre_keys( - &pre_keys_list, 0, 100, omemo->context); - pre_key = signal_protocol_key_helper_key_list_element(pre_keys_list); - signal_protocol_key_helper_key_list_next(pre_keys_list); - - uint32_t id = session_pre_key_get_id(pre_key); - session_pre_key_serialize(&record, pre_key); + signal_protocol_key_helper_pre_key_list_node *pre_keys_list; + session_pre_key *pre_key = NULL; + for (signal_protocol_key_helper_generate_pre_keys(&pre_keys_list, + pks_get_count(omemo, 1), PRE_KEY_COUNT, + omemo->context); pre_keys_list; + pre_keys_list = signal_protocol_key_helper_key_list_next(pre_keys_list)) + { + pre_key = signal_protocol_key_helper_key_list_element(pre_keys_list); + uint32_t id = session_pre_key_get_id(pre_key); + session_pre_key_serialize(record, pre_key); + pks_store_pre_key(id, signal_buffer_data(*record), + signal_buffer_len(*record), user_data); + } signal_protocol_key_helper_key_list_free(pre_keys_list); + } - if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { - weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", - weechat_prefix("error")); - return -1; - } + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; +} - v_pre_key.mv_data = signal_buffer_data(*record); - v_pre_key.mv_size = signal_buffer_len(*record); +int pks_remove_pre_key(uint32_t pre_key_id, void *user_data) +{ + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_pre_key = { + .mv_data = NULL, + .mv_size = strlen("pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_pre_key; - if (mdb_put(transaction, omemo->db->dbi_omemo, - &k_pre_key, &v_pre_key, MDB_NOOVERWRITE)) - { - weechat_printf(NULL, "%sxmpp: failed to write lmdb value", - weechat_prefix("error")); - return -1; - }; + k_pre_key.mv_data = malloc(sizeof(char) * ( + k_pre_key.mv_size + 1)); + k_pre_key.mv_size = + snprintf(k_pre_key.mv_data, k_pre_key.mv_size + 1, + "pre_key_%-10u", pre_key_id); - if (mdb_txn_commit(transaction)) { - weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", - weechat_prefix("error")); - return -1; - }; - */ + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); return -1; } + if (mdb_del(transaction, omemo->db->dbi_omemo, &k_pre_key, + &v_pre_key)) { + weechat_printf(NULL, "%sxmpp: failed to erase lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; } -int pks_store_pre_key(uint32_t pre_key_id, uint8_t *record, size_t record_len, void *user_data) +void pks_destroy_func(void *user_data) { - (void) pre_key_id; - (void) record; - (void) record_len; - (void) user_data; - return -1; struct t_omemo *omemo = (struct t_omemo *)user_data; - MDB_txn *transaction; - MDB_val k_pre_key = mdb_val_str("pre_key"); - MDB_val v_pre_key; + (void) omemo; + // Function called to perform cleanup when the data store context is being destroyed +} - if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { +int spks_load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data) +{ + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_signed_pre_key = { + .mv_data = NULL, + .mv_size = strlen("signed_pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_signed_pre_key; + + k_signed_pre_key.mv_data = malloc(sizeof(char) * ( + k_signed_pre_key.mv_size + 1)); + k_signed_pre_key.mv_size = + snprintf(k_signed_pre_key.mv_data, k_signed_pre_key.mv_size + 1, + "signed_pre_key_%-10u", signed_pre_key_id); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); + return -1; } - if (mdb_get(transaction, omemo->db->dbi_omemo, - &k_pre_key, &v_pre_key)) + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_signed_pre_key, &v_signed_pre_key)) { - *record = signal_buffer_create(v_pre_key.mv_data, v_pre_key.mv_size); + *record = signal_buffer_create(v_signed_pre_key.mv_data, v_signed_pre_key.mv_size); if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; } else { - signal_protocol_key_helper_pre_key_list_node *pre_keys_list; - session_pre_key *pre_key = NULL; - - mdb_txn_abort(transaction); - - /* - signal_protocol_key_helper_generate_pre_keys( - &pre_keys_list, 0, 100, omemo->context); - pre_key = signal_protocol_key_helper_key_list_element(pre_keys_list); - signal_protocol_key_helper_key_list_next(pre_keys_list); - - uint32_t id = session_pre_key_get_id(pre_key); - session_pre_key_serialize(&record, pre_key); - - signal_protocol_key_helper_key_list_free(pre_keys_list); + session_signed_pre_key *signed_pre_key = NULL; + signal_buffer *serialized_key = NULL; - if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { - weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", - weechat_prefix("error")); - return -1; - } + signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, omemo->identity, signed_pre_key_id, time(NULL), omemo->context); + session_signed_pre_key_serialize(&serialized_key, signed_pre_key); - v_pre_key.mv_data = signal_buffer_data(*record); - v_pre_key.mv_size = signal_buffer_len(*record); + v_signed_pre_key.mv_data = signal_buffer_data(serialized_key); + v_signed_pre_key.mv_size = signal_buffer_len(serialized_key); if (mdb_put(transaction, omemo->db->dbi_omemo, - &k_pre_key, &v_pre_key, MDB_NOOVERWRITE)) + &k_signed_pre_key, &v_signed_pre_key, MDB_NOOVERWRITE)) { - weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_printf(NULL, "%sxmpp: failed to read lmdb value", weechat_prefix("error")); - return -1; + goto cleanup; }; if (mdb_txn_commit(transaction)) { weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", weechat_prefix("error")); - return -1; + goto cleanup; }; - */ - return -1; + + *record = serialized_key; } return 0; -} - -int pks_contains_pre_key(uint32_t pre_key_id, void *user_data) -{ - (void) pre_key_id; - (void) user_data; +cleanup: + mdb_txn_abort(transaction); return -1; } -int pks_remove_pre_key(uint32_t pre_key_id, void *user_data) +int spks_store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data) { - (void) pre_key_id; - (void) user_data; - return -1; -} + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_signed_pre_key = { + .mv_data = NULL, + .mv_size = strlen("signed_pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_signed_pre_key = {.mv_data = record, .mv_size = record_len}; -void pks_destroy_func(void *user_data) -{ - (void) user_data; -} + k_signed_pre_key.mv_data = malloc(sizeof(char) * ( + k_signed_pre_key.mv_size + 1)); + k_signed_pre_key.mv_size = + snprintf(k_signed_pre_key.mv_data, k_signed_pre_key.mv_size + 1, + "signed_pre_key_%-10u", signed_pre_key_id); -int spks_load_signed_pre_key(signal_buffer **record, uint32_t signed_pre_key_id, void *user_data) -{ - (void) record; - (void) signed_pre_key_id; - (void) user_data; - return -1; - //session_signed_pre_key *signed_pre_key; - //int start_id = 0; - //time_t timestamp = time(NULL); - //signal_protocol_key_helper_generate_signed_pre_key(&signed_pre_key, new_omemo->identity, 5, timestamp, new_omemo->context); -} + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } -int spks_store_signed_pre_key(uint32_t signed_pre_key_id, uint8_t *record, size_t record_len, void *user_data) -{ - (void) signed_pre_key_id; - (void) record; - (void) record_len; - (void) user_data; + if (mdb_put(transaction, omemo->db->dbi_omemo, &k_signed_pre_key, + &v_signed_pre_key, 0)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); return -1; } int spks_contains_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) { - (void) signed_pre_key_id; - (void) user_data; - return -1; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_signed_pre_key = { + .mv_data = NULL, + .mv_size = strlen("signed_pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_signed_pre_key; + + k_signed_pre_key.mv_data = malloc(sizeof(char) * ( + k_signed_pre_key.mv_size + 1)); + k_signed_pre_key.mv_size = + snprintf(k_signed_pre_key.mv_data, k_signed_pre_key.mv_size + 1, + "signed_pre_key_%-10u", signed_pre_key_id); + + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_get(transaction, omemo->db->dbi_omemo, &k_signed_pre_key, + &v_signed_pre_key)) { + mdb_txn_abort(transaction); + goto cleanup; + }; + + mdb_txn_abort(transaction); + + return 1; +cleanup: + mdb_txn_abort(transaction); + return 0; } int spks_remove_signed_pre_key(uint32_t signed_pre_key_id, void *user_data) { - (void) signed_pre_key_id; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_signed_pre_key = { + .mv_data = NULL, + .mv_size = strlen("signed_pre_key_") + 10, // strlen(UINT32_MAX) + }; + MDB_val v_signed_pre_key; + + k_signed_pre_key.mv_data = malloc(sizeof(char) * ( + k_signed_pre_key.mv_size + 1)); + k_signed_pre_key.mv_size = + snprintf(k_signed_pre_key.mv_data, k_signed_pre_key.mv_size + 1, + "signed_pre_key_%-10u", signed_pre_key_id); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_del(transaction, omemo->db->dbi_omemo, &k_signed_pre_key, + &v_signed_pre_key)) { + weechat_printf(NULL, "%sxmpp: failed to erase lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); return -1; } void spks_destroy_func(void *user_data) { - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + (void) omemo; + // Function called to perform cleanup when the data store context is being destroyed } int ss_load_session_func(signal_buffer **record, signal_buffer **user_record, const signal_protocol_address *address, void *user_data) { - (void) record; - (void) user_record; - (void) address; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_session = { + .mv_data = NULL, + .mv_size = strlen("session_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_session; + MDB_val k_user = { + .mv_data = NULL, + .mv_size = strlen("user_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_user; (void) v_user; (void) user_record; + + k_session.mv_data = malloc(sizeof(char) * (k_session.mv_size + 1)); + k_session.mv_size = + snprintf(k_session.mv_data, k_session.mv_size + 1, + "session_%u_%s", address->device_id, address->name); + k_user.mv_data = malloc(sizeof(char) * (k_user.mv_size + 1)); + k_user.mv_size = + snprintf(k_user.mv_data, k_user.mv_size + 1, + "user_%u_%s", address->device_id, address->name); + + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_get(transaction, omemo->db->dbi_omemo, + &k_session, &v_session)/* || + mdb_get(transaction, omemo->db->dbi_omemo, + &k_user, &v_user)*/) + { + mdb_txn_abort(transaction); + return 0; + } + + *record = signal_buffer_create(v_session.mv_data, v_session.mv_size); + //*user_record = signal_buffer_create(v_user.mv_data, v_user.mv_size); + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 1; +cleanup: + mdb_txn_abort(transaction); return -1; } int ss_get_sub_device_sessions_func(signal_int_list **sessions, const char *name, size_t name_len, void *user_data) { - (void) sessions; - (void) name; - (void) name_len; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_device_ids = { + .mv_data = NULL, + .mv_size = strlen("device_ids_") + name_len, + }; + MDB_val v_device_ids; + + k_device_ids.mv_data = malloc(sizeof(char) * ( + k_device_ids.mv_size + 1)); + snprintf(k_device_ids.mv_data, k_device_ids.mv_size + 1, + "device_ids_%s", name); + + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_device_ids, &v_device_ids)) + { + char **argv; + int argc, i; + signal_int_list *list = signal_int_list_alloc(); + + if (!list) { + goto cleanup; + } + + argv = weechat_string_split(v_device_ids.mv_data, " ", NULL, 0, 0, &argc); + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + for (i = 0; i < argc; i++) + { + char* device_id = argv[i]; + + signal_int_list_push_back(list, strtol(device_id, NULL, 10)); + } + + weechat_string_free_split(argv); + + *sessions = list; + return argc; + } + else + { + mdb_txn_abort(transaction); + return 0; + } +cleanup: + mdb_txn_abort(transaction); return -1; } int ss_store_session_func(const signal_protocol_address *address, uint8_t *record, size_t record_len, uint8_t *user_record, size_t user_record_len, void *user_data) { - (void) address; - (void) record; - (void) record_len; - (void) user_record; - (void) user_record_len; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_session = { + .mv_data = NULL, + .mv_size = strlen("session_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_session = {.mv_data = record, .mv_size = record_len}; + MDB_val k_user = { + .mv_data = NULL, + .mv_size = strlen("user_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_user = {.mv_data = user_record, .mv_size = user_record_len}; (void) v_user; + + k_session.mv_data = malloc(sizeof(char) * ( + k_session.mv_size + 1)); + k_session.mv_size = + snprintf(k_session.mv_data, k_session.mv_size + 1, + "session_%u_%s", address->device_id, address->name); + k_user.mv_data = malloc(sizeof(char) * ( + k_user.mv_size + 1)); + k_user.mv_size = + snprintf(k_user.mv_data, k_user.mv_size + 1, + "user_%u_%s", address->device_id, address->name); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_put(transaction, omemo->db->dbi_omemo, + &k_session, &v_session, 0)/* || + mdb_put(transaction, omemo->db->dbi_omemo, + &k_user, &v_user, 0)*/) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); return -1; } int ss_contains_session_func(const signal_protocol_address *address, void *user_data) { - (void) address; - (void) user_data; - return -1; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_session = { + .mv_data = NULL, + .mv_size = strlen("session_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_session; + + k_session.mv_data = malloc(sizeof(char) * ( + k_session.mv_size + 1)); + k_session.mv_size = + snprintf(k_session.mv_data, k_session.mv_size + 1, + "session_%u_%s", address->device_id, address->name); + + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return 0; + } + + if (mdb_get(transaction, omemo->db->dbi_omemo, &k_session, &v_session)) { + mdb_txn_abort(transaction); + return 0; + }; + + mdb_txn_abort(transaction); + return 1; } int ss_delete_session_func(const signal_protocol_address *address, void *user_data) { - (void) address; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_session = { + .mv_data = NULL, + .mv_size = strlen("session_") + 10 + //strlen(address->device_id) + + 1 + strlen(address->name), + }; + MDB_val v_session; + + k_session.mv_data = malloc(sizeof(char) * ( + k_session.mv_size + 1)); + k_session.mv_size = + snprintf(k_session.mv_data, k_session.mv_size + 1, + "session_%u_%s", address->device_id, address->name); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_del(transaction, omemo->db->dbi_omemo, &k_session, &v_session)) { + weechat_printf(NULL, "%sxmpp: failed to erase lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 1; +cleanup: + mdb_txn_abort(transaction); return -1; } int ss_delete_all_sessions_func(const char *name, size_t name_len, void *user_data) { - (void) name; - (void) name_len; - (void) user_data; + signal_int_list *sessions; + ss_get_sub_device_sessions_func(&sessions, name, name_len, user_data); + + int n = signal_int_list_size(sessions); + for (int i = 0; i < n; i++) + { + signal_protocol_address address = {.name = name, .name_len = name_len, + .device_id = signal_int_list_at(sessions, i)}; + ss_delete_session_func(&address, user_data); + } + signal_int_list_free(sessions); return -1; } void ss_destroy_func(void *user_data) { - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + (void) omemo; + // Function called to perform cleanup when the data store context is being destroyed } int sks_store_sender_key(const signal_protocol_sender_key_name *sender_key_name, uint8_t *record, size_t record_len, uint8_t *user_record, size_t user_record_len, void *user_data) { - (void) sender_key_name; - (void) record; - (void) record_len; - (void) user_record; - (void) user_record_len; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + char *device_list = NULL; + MDB_txn *transaction = NULL; + MDB_val k_sender_key = { + .mv_data = NULL, + .mv_size = strlen("sender_key_") + strlen(sender_key_name->group_id) + + 1 + 10 + //strlen(sender_key_name->sender.device_id) + + 1 + strlen(sender_key_name->sender.name), + }; + MDB_val v_sender_key = {.mv_data = record, .mv_size = record_len}; + MDB_val k_user = { + .mv_data = NULL, + .mv_size = strlen("user_") + strlen(sender_key_name->group_id) + + 1 + 10 + //strlen(sender_key_name->sender.device_id) + + 1 + strlen(sender_key_name->sender.name), + }; + MDB_val v_user = {.mv_data = user_record, .mv_size = user_record_len}; (void) v_user; + MDB_val k_device_ids = { + .mv_data = NULL, + .mv_size = strlen("device_ids_") + strlen(sender_key_name->sender.name), + }; + MDB_val v_device_ids; + + k_sender_key.mv_data = malloc(sizeof(char) * ( + k_sender_key.mv_size + 1)); + k_sender_key.mv_size = + snprintf(k_sender_key.mv_data, k_sender_key.mv_size + 1, + "sender_key_%s_%u_%s", sender_key_name->group_id, + sender_key_name->sender.device_id, + sender_key_name->sender.name); + k_user.mv_data = malloc(sizeof(char) * ( + k_user.mv_size + 1)); + k_user.mv_size = + snprintf(k_user.mv_data, k_user.mv_size + 1, + "user_%s_%u_%s", sender_key_name->group_id, + sender_key_name->sender.device_id, + sender_key_name->sender.name); + k_device_ids.mv_data = malloc(sizeof(char) * ( + k_device_ids.mv_size + 1)); + snprintf(k_device_ids.mv_data, k_device_ids.mv_size + 1, + "device_ids_%s", sender_key_name->sender.name); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (!mdb_get(transaction, omemo->db->dbi_omemo, + &k_device_ids, &v_device_ids)) + { + char **argv; + int argc, i; + + argv = weechat_string_split(v_device_ids.mv_data, " ", NULL, 0, 0, &argc); + for (i = 0; i < argc; i++) + { + char* device_id = argv[i]; + if (strtol(device_id, NULL, 10) == sender_key_name->sender.device_id) break; + } + + weechat_string_free_split(argv); + + if (i == argc) + { + size_t device_list_len = strlen(v_device_ids.mv_data) + 1 + 10 + 1; + device_list = malloc(sizeof(char) * device_list_len); + snprintf(device_list, device_list_len, "%s %u", + (char*)v_device_ids.mv_data, sender_key_name->sender.device_id); + v_device_ids.mv_data = device_list; + v_device_ids.mv_size = strlen(device_list) + 1; + } + } + else + { + device_list = malloc(sizeof(char) * (10 + 1)); + snprintf(device_list, 10 + 1, "%u", sender_key_name->sender.device_id); + v_device_ids.mv_data = device_list; + v_device_ids.mv_size = strlen(device_list) + 1; + } + + if (mdb_put(transaction, omemo->db->dbi_omemo, + &k_sender_key, &v_sender_key, 0)/* || + mdb_put(transaction, omemo->db->dbi_omemo, + &k_user, &v_user, 0)*/ || + mdb_put(transaction, omemo->db->dbi_omemo, + &k_device_ids, &v_device_ids, 0)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + free(device_list); + + return 0; +cleanup: + free(device_list); + mdb_txn_abort(transaction); return -1; } int sks_load_sender_key(signal_buffer **record, signal_buffer **user_record, const signal_protocol_sender_key_name *sender_key_name, void *user_data) { - (void) record; - (void) user_record; - (void) sender_key_name; - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + MDB_txn *transaction = NULL; + MDB_val k_sender_key = { + .mv_data = NULL, + .mv_size = strlen("sender_key_") + strlen(sender_key_name->group_id) + + 1 + 10 + //strlen(sender_key_name->sender.device_id) + + 1 + strlen(sender_key_name->sender.name), + }; + MDB_val v_sender_key; + MDB_val k_user = { + .mv_data = NULL, + .mv_size = strlen("user_") + strlen(sender_key_name->group_id) + + 1 + 10 + //strlen(sender_key_name->sender.device_id) + + 1 + strlen(sender_key_name->sender.name), + }; + MDB_val v_user; (void) v_user; (void) user_record; + + k_sender_key.mv_data = malloc(sizeof(char) * ( + k_sender_key.mv_size + 1)); + k_sender_key.mv_size = + snprintf(k_sender_key.mv_data, k_sender_key.mv_size + 1, + "sender_key_%s_%u_%s", sender_key_name->group_id, + sender_key_name->sender.device_id, + sender_key_name->sender.name); + k_user.mv_data = malloc(sizeof(char) * ( + k_user.mv_size + 1)); + k_user.mv_size = + snprintf(k_user.mv_data, k_user.mv_size + 1, + "user_%s_%u_%s", sender_key_name->group_id, + sender_key_name->sender.device_id, + sender_key_name->sender.name); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_get(transaction, omemo->db->dbi_omemo, + &k_sender_key, &v_sender_key)/* && + mdb_get(transaction, omemo->db->dbi_omemo, + &k_user, &v_user)*/) + { + *record = signal_buffer_create(v_sender_key.mv_data, v_sender_key.mv_size); + //*user_record = signal_buffer_create(v_user.mv_data, v_user.mv_size); + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + } + else + { + goto cleanup; + } + + return 0; +cleanup: + mdb_txn_abort(transaction); return -1; } void sks_destroy_func(void *user_data) { - (void) user_data; + struct t_omemo *omemo = (struct t_omemo *)user_data; + (void) omemo; + // Function called to perform cleanup when the data store context is being destroyed +} + +int dls_store_devicelist(const char *jid, signal_int_list *devicelist, struct t_omemo *omemo) +{ + MDB_txn *transaction = NULL; + MDB_val k_devicelist = { + .mv_data = NULL, + .mv_size = strlen("devicelist_") + strlen(jid), + }; + MDB_val v_devicelist; + + k_devicelist.mv_data = malloc(sizeof(char) * ( + k_devicelist.mv_size + 1)); + k_devicelist.mv_size = + snprintf(k_devicelist.mv_data, k_devicelist.mv_size + 1, + "devicelist_%s", jid); + char *devices[128] = {0}; + for (size_t i = 0; i < signal_int_list_size(devicelist); i++) + { + int device = signal_int_list_at(devicelist, i); + devices[i] = malloc(sizeof(*devices) * (10 + 1)); + devices[i+1] = NULL; + snprintf(devices[i], 10 + 1, "%u", device); + } + v_devicelist.mv_data = weechat_string_build_with_split_string( + (const char **)devices, ";"); + v_devicelist.mv_size = strlen(v_devicelist.mv_data); + for (char **device = (char **)devices; *device; device++) free(*device); + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_put(transaction, omemo->db->dbi_omemo, &k_devicelist, + &v_devicelist, 0)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value", + weechat_prefix("error")); + goto cleanup; + }; + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; +} + +int dls_load_devicelist(signal_int_list **devicelist, const char *jid, struct t_omemo *omemo) +{ + MDB_txn *transaction = NULL; + MDB_val k_devicelist = { + .mv_data = NULL, + .mv_size = strlen("devicelist_") + strlen(jid), + }; + MDB_val v_devicelist; + + k_devicelist.mv_data = malloc(sizeof(char) * ( + k_devicelist.mv_size + 1)); + k_devicelist.mv_size = + snprintf(k_devicelist.mv_data, k_devicelist.mv_size + 1, + "devicelist_%s", jid); + + if (mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if (mdb_get(transaction, omemo->db->dbi_omemo, + &k_devicelist, &v_devicelist)) + { + goto cleanup; + } + + *devicelist = signal_int_list_alloc(); + int devices_len = 0; + char **devices = weechat_string_split(v_devicelist.mv_data, ";", NULL, 0, 0, &devices_len); + for (int i = 0; i < devices_len; i++) + { + char* device_id = devices[i]; + signal_int_list_push_back(*devicelist, strtol(device_id, NULL, 10)); + } + weechat_string_free_split(devices); + + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; +} + +int bks_store_bundle(signal_protocol_address *address, + struct t_pre_key **pre_keys, struct t_pre_key **signed_pre_keys, + const char *signature, const char *identity_key, struct t_omemo *omemo) +{ + size_t n_pre_keys = -1; + while (pre_keys[++n_pre_keys] != NULL); + char **pre_key_buffers = malloc(sizeof(char*) * (n_pre_keys + 1)); + for (size_t i = 0; i < n_pre_keys; i++) + { + struct t_pre_key *pre_key = pre_keys[i]; + size_t keylen = 10 + strlen(pre_key->public_key) + 1; + pre_key_buffers[i] = malloc(sizeof(char) * keylen); + pre_key_buffers[i+1] = NULL; + snprintf(pre_key_buffers[i], keylen, + "%s.%s", pre_key->id, pre_key->public_key); + } + + size_t n_signed_pre_keys = -1; + while (signed_pre_keys[++n_signed_pre_keys] != NULL); + char **signed_pre_key_buffers = malloc(sizeof(char*) * (n_signed_pre_keys + 1)); + for (size_t i = 0; i < n_signed_pre_keys; i++) + { + struct t_pre_key *signed_pre_key = signed_pre_keys[i]; + size_t keylen = 10 + 1 + strlen(signed_pre_key->public_key); + signed_pre_key_buffers[i] = malloc(sizeof(char) * (keylen + 1)); + signed_pre_key_buffers[i+1] = NULL; + snprintf(signed_pre_key_buffers[i], keylen + 1, + "%s.%s", signed_pre_key->id, signed_pre_key->public_key); + + int ret; + uint8_t *signing_key_buf; + size_t signing_key_len = base64_decode(identity_key, + strlen(identity_key), &signing_key_buf); + ec_public_key *signing_key; + if ((ret = curve_decode_point(&signing_key, signing_key_buf, + signing_key_len, omemo->context))) { + weechat_printf(NULL, "%sxmpp: failed to decode ED25519 prekey", + weechat_prefix("error")); + goto cleanup; + }; + uint8_t *signed_key_buf; + size_t signed_key_len = base64_decode(signed_pre_key->public_key, + strlen(signed_pre_key->public_key), &signed_key_buf); + uint8_t *signature_buf; + size_t signature_len = base64_decode(signature, + strlen(signature), &signature_buf); + int valid = curve_verify_signature(signing_key, + signed_key_buf, signed_key_len, + signature_buf, signature_len); + if (valid <= 0) { + weechat_printf(NULL, "%somemo: failed to validate ED25519 signature for %s:%u", + weechat_prefix("error"), address->name, address->device_id); + } + } + + MDB_txn *transaction = NULL; + const char *jid = address->name; + uint32_t device_id = address->device_id; + size_t keylen = strlen("bundle_??_") + strlen(jid) + 1 + 10 + 1; + MDB_val k_bundle_pk = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_pk.mv_size = snprintf(k_bundle_pk.mv_data, keylen, + "bundle_pk_%s_%u", jid, device_id); + MDB_val k_bundle_sk = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_sk.mv_size = snprintf(k_bundle_sk.mv_data, keylen, + "bundle_sk_%s_%u", jid, device_id); + MDB_val k_bundle_sg = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_sg.mv_size = snprintf(k_bundle_sg.mv_data, keylen, + "bundle_sg_%s_%u", jid, device_id); + MDB_val k_bundle_ik = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_ik.mv_size = snprintf(k_bundle_ik.mv_data, keylen, + "bundle_ik_%s_%u", jid, device_id); + + MDB_val v_bundle_pk = { + .mv_data = weechat_string_build_with_split_string( + (const char **)pre_key_buffers, ";"), + .mv_size = 0, + }; + v_bundle_pk.mv_size = strlen(v_bundle_pk.mv_data) + 1; + MDB_val v_bundle_sk = { + .mv_data = weechat_string_build_with_split_string( + (const char **)signed_pre_key_buffers, ";"), + .mv_size = 0, + }; + v_bundle_sk.mv_size = strlen(v_bundle_sk.mv_data) + 1; + MDB_val v_bundle_sg = { + .mv_data = (char*)signature, + .mv_size = strlen(signature), + }; + MDB_val v_bundle_ik = { + .mv_data = (char*)identity_key, + .mv_size = strlen(identity_key), + }; + + if (mdb_txn_begin(omemo->db->env, NULL, 0, &transaction)) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + int ret; + if ((ret = mdb_put(transaction, omemo->db->dbi_omemo, &k_bundle_pk, + &v_bundle_pk, 0)) || + (ret = mdb_put(transaction, omemo->db->dbi_omemo, &k_bundle_sk, + &v_bundle_sk, 0)) || + (ret = mdb_put(transaction, omemo->db->dbi_omemo, &k_bundle_sg, + &v_bundle_sg, 0)) || + (ret = mdb_put(transaction, omemo->db->dbi_omemo, &k_bundle_ik, + &v_bundle_ik, 0))) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb value '%s'@%u: %s", + weechat_prefix("error"), v_bundle_pk.mv_data, v_bundle_pk.mv_size, mdb_strerror(ret)); + goto cleanup; + }; + + if ((ret = mdb_txn_commit(transaction))) { + weechat_printf(NULL, "%sxmpp: failed to write lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + mdb_txn_abort(transaction); + return -1; +} + +int bks_load_bundle(session_pre_key_bundle **bundle, signal_protocol_address *address, struct t_omemo *omemo) +{ + MDB_txn *transaction = NULL; + const char *jid = address->name; + uint32_t device_id = address->device_id; + size_t keylen = strlen("bundle_??_") + address->name_len + 1 + 10 + 1; + MDB_val k_bundle_pk = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_pk.mv_size = snprintf(k_bundle_pk.mv_data, keylen, + "bundle_pk_%s_%u", jid, device_id); + MDB_val k_bundle_sk = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_sk.mv_size = snprintf(k_bundle_sk.mv_data, keylen, + "bundle_sk_%s_%u", jid, device_id); + MDB_val k_bundle_sg = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_sg.mv_size = snprintf(k_bundle_sg.mv_data, keylen, + "bundle_sg_%s_%u", jid, device_id); + MDB_val k_bundle_ik = { + .mv_data = malloc(sizeof(char) * (keylen + 1)), + .mv_size = 0, + }; + k_bundle_ik.mv_size = snprintf(k_bundle_ik.mv_data, keylen, + "bundle_ik_%s_%u", jid, device_id); + + MDB_val v_bundle_pk; + MDB_val v_bundle_sk; + MDB_val v_bundle_sg; + MDB_val v_bundle_ik; + + int ret; + if ((ret = mdb_txn_begin(omemo->db->env, NULL, MDB_RDONLY, &transaction))) { + weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", + weechat_prefix("error")); + return -1; + } + + if ((ret = mdb_get(transaction, omemo->db->dbi_omemo, + &k_bundle_pk, &v_bundle_pk)) || + (ret = mdb_get(transaction, omemo->db->dbi_omemo, + &k_bundle_sk, &v_bundle_sk)) || + (ret = mdb_get(transaction, omemo->db->dbi_omemo, + &k_bundle_sg, &v_bundle_sg)) || + (ret = mdb_get(transaction, omemo->db->dbi_omemo, + &k_bundle_ik, &v_bundle_ik))) + { + goto cleanup; + } + + int bundle_pk_len = 0; + char **bundle_pks = weechat_string_split(v_bundle_pk.mv_data, ";", NULL, 0, 0, &bundle_pk_len); + uint32_t pre_key_id = 0; + ec_public_key *pre_key; + //for (int i = 0; i < bundle_pk_len; i++) + { + int i = rand() % bundle_pk_len; + char *bundle_pk = bundle_pks[i]; + pre_key_id = strtol(bundle_pk, NULL, 10); + char *key_data = memchr(bundle_pk, '.', 10 + 1) + 1; + uint8_t *key_buf; + size_t key_len = base64_decode(key_data, strlen(key_data), &key_buf); + if ((ret = curve_decode_point(&pre_key, key_buf, key_len, omemo->context))) { + weechat_printf(NULL, "%sxmpp: failed to decode ED25519 prekey", + weechat_prefix("error")); + goto cleanup; + }; + } + int bundle_sk_len; + char **bundle_sks = weechat_string_split(v_bundle_sk.mv_data, ";", NULL, 0, 0, &bundle_sk_len); + uint32_t signed_pre_key_id; + ec_public_key *signed_pre_key; + //for (int i = 0; i < bundle_sk_len; i++) + { + int i = rand() % bundle_sk_len; + char *bundle_sk = bundle_sks[i]; + signed_pre_key_id = strtol(bundle_sk, NULL, 10); + char *key_data = memchr(bundle_sk, '.', 10 + 1) + 1; + uint8_t *key_buf; + size_t key_len = base64_decode(key_data, strlen(key_data), &key_buf); + if ((ret = curve_decode_point(&signed_pre_key, key_buf, key_len, omemo->context))) { + weechat_printf(NULL, "%sxmpp: failed to decode ED25519 signed prekey", + weechat_prefix("error")); + goto cleanup; + }; + } + uint8_t *sig_buf; + size_t sig_len = base64_decode(v_bundle_sg.mv_data, v_bundle_sg.mv_size, &sig_buf); + signal_buffer *signature = signal_buffer_create(sig_buf, sig_len); + ec_public_key *identity_key; + uint8_t *key_buf; + size_t key_len = base64_decode(v_bundle_ik.mv_data, v_bundle_ik.mv_size, &key_buf); + if ((ret = curve_decode_point(&identity_key, key_buf, key_len, omemo->context))) { + weechat_printf(NULL, "%sxmpp: failed to decode ED25519 identity key", + weechat_prefix("error")); + goto cleanup; + }; + + if ((ret = session_pre_key_bundle_create(bundle, device_id, device_id/*?*/, pre_key_id, pre_key, signed_pre_key_id, signed_pre_key, signal_buffer_data(signature), signal_buffer_len(signature), identity_key))) { + weechat_printf(NULL, "%sxmpp: failed to create OMEMO prekey bundle", + weechat_prefix("error")); + goto cleanup; + }; + + if ((ret = mdb_txn_commit(transaction))) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + goto cleanup; + }; + + return 0; +cleanup: + /* + void session_pre_key_bundle_destroy(signal_type_base *type); + */ + mdb_txn_abort(transaction); + return -1; } void omemo__log_emit_weechat(int level, const char *message, size_t len, void *user_data) @@ -940,13 +1963,119 @@ void omemo__log_emit_weechat(int level, const char *message, size_t len, void *u const char *tags = level < SG_LOG_DEBUG ? "no_log" : NULL; + (void)buffer; weechat_printf_date_tags( - buffer, 0, tags, + NULL, 0, tags, _("%somemo (%s): %.*s"), weechat_prefix("network"), log_level_name[level], len, message); } +xmpp_stanza_t *omemo__get_bundle(xmpp_ctx_t *context, char *from, char *to, + struct t_omemo *omemo) +{ + xmpp_stanza_t **children = malloc(sizeof(*children) * (100 + 1)); + xmpp_stanza_t *parent = NULL; + signal_buffer *record = NULL; + ec_key_pair *keypair = NULL; + ec_public_key *public_key = NULL; + + int num_keys = 0; + for (uint32_t id = PRE_KEY_START; + id < INT_MAX && num_keys < 100; id++) + { + if (pks_load_pre_key(&record, id, omemo) != 0) continue; + else num_keys++; + session_pre_key *pre_key = NULL; + session_pre_key_deserialize(&pre_key, signal_buffer_data(record), + signal_buffer_len(record), omemo->context); + if (pre_key == 0) (*((int*)0))++; + signal_buffer_free(record); + keypair = session_pre_key_get_key_pair(pre_key); + public_key = ec_key_pair_get_public(keypair); + ec_public_key_serialize(&record, public_key); + char *data = NULL; + base64_encode(signal_buffer_data(record), + signal_buffer_len(record), &data); + signal_buffer_free(record); + if (pre_key) session_pre_key_destroy((signal_type_base*)pre_key); + //SIGNAL_UNREF(pre_key); + char *id_str = malloc(sizeof(char) * (10 + 1)); + snprintf(id_str, 10+1, "%u", id); + children[num_keys-1] = stanza__iq_pubsub_publish_item_bundle_prekeys_preKeyPublic( + context, NULL, NULL, with_free(id_str)); + stanza__set_text(context, children[num_keys-1], with_free(data)); + } + children[100] = NULL; + + children[3] = stanza__iq_pubsub_publish_item_bundle_prekeys( + context, NULL, children); + children[4] = NULL; + + spks_load_signed_pre_key(&record, 1, omemo); + session_signed_pre_key *signed_pre_key; + session_signed_pre_key_deserialize(&signed_pre_key, + signal_buffer_data(record), signal_buffer_len(record), + omemo->context); + signal_buffer_free(record); + uint32_t signed_pre_key_id = session_signed_pre_key_get_id(signed_pre_key); + keypair = session_signed_pre_key_get_key_pair(signed_pre_key); + public_key = ec_key_pair_get_public(keypair); + ec_public_key_serialize(&record, public_key); + char *signed_pre_key_public = NULL; + base64_encode(signal_buffer_data(record), signal_buffer_len(record), + &signed_pre_key_public); + signal_buffer_free(record); + char *signed_pre_key_id_str = malloc(sizeof(char) * (10 + 1)); + snprintf(signed_pre_key_id_str, 10+1, "%u", signed_pre_key_id); + children[0] = stanza__iq_pubsub_publish_item_bundle_signedPreKeyPublic( + context, NULL, NULL, with_free(signed_pre_key_id_str)); + stanza__set_text(context, children[0], with_free(signed_pre_key_public)); + + const uint8_t *keysig = session_signed_pre_key_get_signature(signed_pre_key); + size_t keysig_len = session_signed_pre_key_get_signature_len(signed_pre_key); + char *signed_pre_key_signature = NULL; + base64_encode(keysig, keysig_len, &signed_pre_key_signature); + session_pre_key_destroy((signal_type_base*)signed_pre_key); + children[1] = stanza__iq_pubsub_publish_item_bundle_signedPreKeySignature( + context, NULL, NULL); + stanza__set_text(context, children[1], with_free(signed_pre_key_signature)); + + iks_get_identity_key_pair(&record, (signal_buffer**)&signed_pre_key, omemo); + char *identity_key = NULL; + base64_encode(signal_buffer_data(record), signal_buffer_len(record), + &identity_key); + signal_buffer_free(record); + children[2] = stanza__iq_pubsub_publish_item_bundle_identityKey( + context, NULL, NULL); + stanza__set_text(context, children[2], with_free(identity_key)); + + children[0] = stanza__iq_pubsub_publish_item_bundle( + context, NULL, children, with_noop("eu.siacs.conversations.axolotl")); + children[1] = NULL; + + children[0] = stanza__iq_pubsub_publish_item( + context, NULL, children, NULL); + + size_t bundle_node_len = strlen("eu.siacs.conversations.axolotl.bundles:") + 10; + char *bundle_node = malloc(sizeof(char) * (bundle_node_len + 1)); + snprintf(bundle_node, bundle_node_len+1, + "eu.siacs.conversations.axolotl.bundles:%u", omemo->device_id); + children[0] = stanza__iq_pubsub_publish( + context, NULL, children, with_free(bundle_node)); + + omemo__handle_bundle(omemo, from, omemo->device_id, children[0]); + + children[0] = stanza__iq_pubsub( + context, NULL, children, with_noop("http://jabber.org/protocol/pubsub")); + + parent = stanza__iq( + context, NULL, children, NULL, "announce2", from, to, "set"); + free(children); + + return parent; +} + void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, const char *account_name) { @@ -961,33 +2090,41 @@ void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, signal_context_create(&new_omemo->context, buffer); signal_context_set_log_function(new_omemo->context, &omemo__log_emit_weechat); + int ret; mdb_env_create(&new_omemo->db->env); mdb_env_set_maxdbs(new_omemo->db->env, 50); - mdb_env_set_mapsize(new_omemo->db->env, (size_t)1048576 * 100000); // 1MB * 100000 - char *path = weechat_string_eval_expression("${weechat_data_dir}/xmpp.omemo.db", - NULL, NULL, NULL); - if (mdb_env_open(new_omemo->db->env, path, MDB_NOSUBDIR, 0664) != 0) + mdb_env_set_mapsize(new_omemo->db->env, (size_t)1048576 * 8000); // 8000MB map for valgrind + new_omemo->db_path = weechat_string_eval_expression( + "${weechat_data_dir}/xmpp.omemo.db", NULL, NULL, NULL); + if ((ret = mdb_env_open(new_omemo->db->env, new_omemo->db_path, MDB_NOSUBDIR, 0664)) != 0) { + weechat_printf(NULL, "%sxmpp: failed to open environment file. %s", + weechat_prefix("error"), mdb_strerror(ret)); return; } - free(path); MDB_txn *parentTransaction = NULL; - MDB_txn *transaction; - if (mdb_txn_begin(new_omemo->db->env, parentTransaction, 0 ? MDB_RDONLY : 0, &transaction)) { + MDB_txn *transaction = NULL; + if (mdb_txn_begin(new_omemo->db->env, parentTransaction, 0, &transaction)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb transaction", weechat_prefix("error")); + return; } size_t db_name_len = strlen("omemo_") + strlen(account_name); char *db_name = malloc(sizeof(char) * (db_name_len + 1)); - snprintf(db_name, db_name_len+1, "identity_key_%s", account_name); - if (mdb_dbi_open(transaction, db_name, MDB_DUPSORT | MDB_CREATE, &new_omemo->db->dbi_omemo)) { + snprintf(db_name, db_name_len+1, "omemo_%s", account_name); + if (mdb_dbi_open(transaction, db_name, MDB_CREATE, &new_omemo->db->dbi_omemo)) { weechat_printf(NULL, "%sxmpp: failed to open lmdb database", weechat_prefix("error")); + return; } - mdb_txn_abort(transaction); + if (mdb_txn_commit(transaction)) { + weechat_printf(NULL, "%sxmpp: failed to close lmdb transaction", + weechat_prefix("error")); + return; + }; struct signal_crypto_provider crypto_provider = { .random_func = &cp_random_generator, @@ -1069,64 +2206,405 @@ void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, signal_protocol_store_context_set_sender_key_store( new_omemo->store_context, &sender_key_store); + signal_buffer *public_data, *private_data; + iks_get_local_registration_id(new_omemo, &new_omemo->device_id); + if (!iks_get_identity_key_pair(&public_data, &private_data, new_omemo)) + { + ec_public_key *public_key = NULL; + ec_private_key *private_key = NULL; + curve_decode_point(&public_key, signal_buffer_data(public_data), + signal_buffer_len(public_data), new_omemo->context); + curve_decode_private_point(&private_key, signal_buffer_data(private_data), + signal_buffer_len(private_data), new_omemo->context); + ratchet_identity_key_pair_create(&new_omemo->identity, public_key, private_key); + } + weechat_printf(buffer, "%somemo: device = %d", + weechat_prefix("info"), new_omemo->device_id); + *omemo = new_omemo; } -void omemo__serialize(struct t_omemo *omemo, char **device, - char **identity, size_t *identity_len) +void omemo__handle_devicelist(struct t_omemo *omemo, const char *jid, + xmpp_stanza_t *items) { - if (device) + (void) omemo; + + xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(items, "item"); + if (!item) return; + xmpp_stanza_t *list = xmpp_stanza_get_child_by_name(item, "list"); + if (!list) return; + signal_int_list *devicelist = signal_int_list_alloc(); + for (xmpp_stanza_t *device = xmpp_stanza_get_children(list); + device; device = xmpp_stanza_get_next(device)) { - size_t id_slen = log10(omemo->device_id) * 2; - char *id = malloc(sizeof(char) * id_slen); - snprintf(id, id_slen, "%d", omemo->device_id); + const char *name = xmpp_stanza_get_name(device); + if (weechat_strcasecmp(name, "device") != 0) + continue; + + const char *device_id = xmpp_stanza_get_id(device); + if (!device_id) + continue; - *device = id; + signal_int_list_push_back(devicelist, strtol(device_id, NULL, 10)); } - if (identity) + if (dls_store_devicelist(jid, devicelist, omemo)) + weechat_printf(NULL, "%somemo: failed to handle devicelist (%s)", + weechat_prefix("error"), jid); + signal_int_list_free(devicelist); +} + +void omemo__handle_bundle(struct t_omemo *omemo, const char *jid, + uint32_t device_id, xmpp_stanza_t *items) +{ + xmpp_stanza_t *item = xmpp_stanza_get_child_by_name(items, "item"); + if (!item) return; + xmpp_stanza_t *bundle = xmpp_stanza_get_child_by_name(item, "bundle"); + if (!bundle) return; + xmpp_stanza_t *signedprekey = xmpp_stanza_get_child_by_name(bundle, "signedPreKeyPublic"); + if (!signedprekey) return; + const char *signed_pre_key = xmpp_stanza_get_text(signedprekey); + if (!signed_pre_key) return; + const char *signed_pre_key_id = xmpp_stanza_get_attribute(signedprekey, "signedPreKeyId"); + if (!signed_pre_key_id) return; + xmpp_stanza_t *signature = xmpp_stanza_get_child_by_name(bundle, "signedPreKeySignature"); + if (!signature) return; + const char *key_signature = xmpp_stanza_get_text(signature); + if (!key_signature) return; + xmpp_stanza_t *identitykey = xmpp_stanza_get_child_by_name(bundle, "identityKey"); + if (!identitykey) return; + const char *identity_key = xmpp_stanza_get_text(identitykey); + if (!identity_key) return; + xmpp_stanza_t *prekeys = xmpp_stanza_get_child_by_name(bundle, "prekeys"); + if (!prekeys) return; + + int num_prekeys = 0; + for (xmpp_stanza_t *prekey = xmpp_stanza_get_children(prekeys); + prekey; prekey = xmpp_stanza_get_next(prekey)) + num_prekeys++; + struct t_pre_key **pre_keys = malloc(sizeof(struct t_pre_key) * num_prekeys); + + num_prekeys = -1; + char **format = weechat_string_dyn_alloc(256); + weechat_string_dyn_concat(format, "omemo bundle %s/%u:\n%s..SPK %u: %s\n%3$s..SKS: %s\n%3$s..IK: %s", -1); + for (xmpp_stanza_t *prekey = xmpp_stanza_get_children(prekeys); + prekey; prekey = xmpp_stanza_get_next(prekey)) { - signal_buffer *buffer; - ratchet_identity_key_pair_serialize(&buffer, omemo->identity); + const char *name = xmpp_stanza_get_name(prekey); + if (weechat_strcasecmp(name, "preKeyPublic") != 0) + continue; + + const char *pre_key_id = xmpp_stanza_get_attribute(prekey, "preKeyId"); + if (!pre_key_id) + continue; + const char *pre_key = xmpp_stanza_get_text(prekey); + if (!pre_key) + continue; + + pre_keys[++num_prekeys] = malloc(sizeof(struct t_pre_key)); + *pre_keys[num_prekeys] = (struct t_pre_key) { + .id = pre_key_id, + .public_key = pre_key, + }; - size_t key_slen = signal_buffer_len(buffer) * 2; - char *key = malloc(sizeof(char) * key_slen); - size_t length = weechat_string_base_encode(64, (char*)signal_buffer_data(buffer), - signal_buffer_len(buffer), key); + weechat_string_dyn_concat(format, "\n%3$s..PK ", -1); + weechat_string_dyn_concat(format, pre_key_id, -1); + weechat_string_dyn_concat(format, ": ", -1); + weechat_string_dyn_concat(format, pre_key, -1); + } + pre_keys[num_prekeys] = NULL; + weechat_string_dyn_free(format, 1); + + struct t_pre_key signed_key = { + .id = signed_pre_key_id, + .public_key = signed_pre_key, + }; + struct t_pre_key *signed_pre_keys[2] = { &signed_key, NULL }; - *identity = key; - if (identity_len) - *identity_len = length; + signal_protocol_address address = { + .name = jid, .name_len = strlen(jid), .device_id = device_id }; + { + ec_public_key *key; + uint8_t *key_buf; + size_t key_len = base64_decode(identity_key, + strlen(identity_key), &key_buf); + curve_decode_point(&key, key_buf, key_len, omemo->context); + signal_protocol_identity_save_identity(omemo->store_context, + &address, key); } + bks_store_bundle(&address, pre_keys, signed_pre_keys, + key_signature, identity_key, omemo); } -void omemo__deserialize(struct t_omemo *omemo, const char *device, - const char *identity, size_t identity_len) +char *omemo__decode(struct t_account *account, const char *jid, + xmpp_stanza_t *encrypted) { - if (device) + struct t_omemo *omemo = account->omemo; + uint8_t *key_data = NULL, *tag_data = NULL, *iv_data = NULL, *payload_data = NULL; + size_t key_len = 0, tag_len = 0, iv_len = 0, payload_len = 0; + + xmpp_stanza_t *header = xmpp_stanza_get_child_by_name(encrypted, "header"); + if (!header) return NULL; + xmpp_stanza_t *iv = xmpp_stanza_get_child_by_name(header, "iv"); + if (!iv) return NULL; + const char *iv__text = xmpp_stanza_get_text(iv); + if (!iv__text) return NULL; + iv_len = base64_decode(iv__text, strlen(iv__text), &iv_data); + if (iv_len != AES_IV_SIZE) return NULL; + + char **format = weechat_string_dyn_alloc(256); + weechat_string_dyn_concat(format, "omemo msg %s:\n%s..IV: %s", -1); + for (xmpp_stanza_t *key = xmpp_stanza_get_children(header); + key; key = xmpp_stanza_get_next(key)) { - uint32_t id = device[0] ? atoi(device) : 0; + const char *name = xmpp_stanza_get_name(key); + if (weechat_strcasecmp(name, "key") != 0) + continue; + + const char *key_prekey = xmpp_stanza_get_attribute(key, "prekey"); + const char *key_id = xmpp_stanza_get_attribute(key, "rid"); + if (!key_id) + continue; + if (strtol(key_id, NULL, 10) != omemo->device_id) + continue; + xmpp_stanza_t *key_text = xmpp_stanza_get_children(key); + const char *data = key_text ? xmpp_stanza_get_text(key_text) : NULL; + if (!data) + continue; + key_len = base64_decode(data, strlen(data), &key_data); + + weechat_string_dyn_concat(format, "\n%2$s..K ", -1); + if (key_prekey) + weechat_string_dyn_concat(format, "*", -1); + weechat_string_dyn_concat(format, key_id, -1); + weechat_string_dyn_concat(format, ": ", -1); + weechat_string_dyn_concat(format, data, -1); + + const char *source_id = xmpp_stanza_get_attribute(header, "sid"); + if (!source_id) + continue; + + int ret; + signal_protocol_address address = { + .name = jid, .name_len = strlen(jid), .device_id = strtol(source_id, NULL, 10) }; + signal_message *key_message = NULL; + signal_buffer *aes_key = NULL; + if (key_prekey) { + pre_key_signal_message *pre_key_message = NULL; + if ((ret = pre_key_signal_message_deserialize(&pre_key_message, + key_data, key_len, omemo->context))) return NULL; + ec_public_key *identity_key = pre_key_signal_message_get_identity_key(pre_key_message); + //uint32_t device_id = pre_key_signal_message_get_registration_id(pre_key_message); + //uint32_t pre_key_id = pre_key_signal_message_get_pre_key_id(pre_key_message); + //uint32_t signed_key_id = pre_key_signal_message_get_signed_pre_key_id(pre_key_message); + //ec_public_key *base_key = pre_key_signal_message_get_base_key(pre_key_message); + key_message = pre_key_signal_message_get_signal_message(pre_key_message); + signal_buffer *identity_buf; + if ((ret = ec_public_key_serialize(&identity_buf, identity_key))) return NULL; + if ((ret = iks_save_identity(&address, signal_buffer_data(identity_buf), + signal_buffer_len(identity_buf), omemo))) return NULL; + + session_cipher *cipher; + if ((ret = session_cipher_create(&cipher, omemo->store_context, + &address, omemo->context))) return NULL; + if ((ret = session_cipher_decrypt_pre_key_signal_message(cipher, + pre_key_message, + 0, &aes_key))) return NULL; + } else { + if ((ret = signal_message_deserialize(&key_message, + key_data, key_len, omemo->context))) return NULL; + session_cipher *cipher; + if ((ret = session_cipher_create(&cipher, omemo->store_context, + &address, omemo->context))) return NULL; + if ((ret = session_cipher_decrypt_signal_message(cipher, key_message, + 0, &aes_key))) return NULL; + } - omemo->device_id = id; + if (!aes_key) return NULL; + key_data = signal_buffer_data(aes_key); + key_len = signal_buffer_len(aes_key); + if (key_len >= AES_KEY_SIZE) { + tag_len = key_len - AES_KEY_SIZE; + tag_data = key_data + AES_KEY_SIZE; + key_len = AES_KEY_SIZE; + } + else + { + return NULL; + } + + char *aes_key64 = NULL; + if (base64_encode(key_data, key_len, &aes_key64) && aes_key64) + { + weechat_string_dyn_concat(format, "\n%2$s..AES: ", -1); + weechat_string_dyn_concat(format, aes_key64, -1); + weechat_string_dyn_concat(format, " (", -1); + snprintf(aes_key64, strlen(aes_key64), "%lu", key_len); + weechat_string_dyn_concat(format, aes_key64, -1); + weechat_string_dyn_concat(format, ")", -1); + } + if (tag_len && base64_encode(tag_data, tag_len, &aes_key64) && aes_key64) + { + weechat_string_dyn_concat(format, "\n%2$s..TAG: ", -1); + weechat_string_dyn_concat(format, aes_key64, -1); + weechat_string_dyn_concat(format, " (", -1); + snprintf(aes_key64, strlen(aes_key64), "%lu", tag_len); + weechat_string_dyn_concat(format, aes_key64, -1); + weechat_string_dyn_concat(format, ")", -1); + } } - if (identity) + + xmpp_stanza_t *payload = xmpp_stanza_get_child_by_name(encrypted, "payload"); + if (payload && (payload = xmpp_stanza_get_children(payload))) { - uint8_t *key = malloc(sizeof(uint8_t) * identity_len); - size_t length = weechat_string_base_decode(64, identity, (char*)key); + const char *payload_text = xmpp_stanza_get_text(payload); + if (!payload_text) return NULL; + payload_len = base64_decode(payload_text, strlen(payload_text), &payload_data); + weechat_string_dyn_concat(format, "\n%2$s..PL: ", -1); + weechat_string_dyn_concat(format, payload_text, -1); + } + //weechat_printf(NULL, *format, jid, weechat_color("red"), iv__text); + weechat_string_dyn_free(format, 1); + + if (!(payload_data && iv_data && key_data)) return NULL; + if (iv_len != AES_IV_SIZE || key_len != AES_KEY_SIZE) return NULL; + char *plaintext = NULL; size_t plaintext_len = 0; + if (aes_decrypt(payload_data, payload_len, key_data, iv_data, tag_data, tag_len, + (uint8_t**)&plaintext, &plaintext_len) || plaintext) + { + plaintext[plaintext_len] = '\0'; + return plaintext; + } + return NULL; +} - ratchet_identity_key_pair_deserialize(&omemo->identity, - key, length, omemo->context); +xmpp_stanza_t *omemo__encode(struct t_account *account, const char *jid, + const char *unencrypted) +{ + struct t_omemo *omemo = account->omemo; + uint8_t *key = NULL; uint8_t *iv = NULL; + uint8_t *tag = NULL; size_t tag_len = 0; + uint8_t *ciphertext = NULL; size_t ciphertext_len = 0; + aes_encrypt((uint8_t*)unencrypted, strlen(unencrypted), + &key, &iv, &tag, &tag_len, + &ciphertext, &ciphertext_len); + + uint8_t *key_and_tag = malloc(sizeof(uint8_t) * (AES_KEY_SIZE+tag_len)); + memcpy(key_and_tag, key, AES_KEY_SIZE); + free(key); + memcpy(key_and_tag+AES_KEY_SIZE, tag, tag_len); + free(tag); + char *key64 = NULL; + base64_encode(key_and_tag, AES_KEY_SIZE+tag_len, &key64); + char *iv64 = NULL; + base64_encode(iv, AES_IV_SIZE, &iv64); + free(iv); + char *ciphertext64 = NULL; + base64_encode(ciphertext, ciphertext_len, &ciphertext64); + free(ciphertext); + + xmpp_stanza_t *encrypted = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(encrypted, "encrypted"); + xmpp_stanza_set_ns(encrypted, "eu.siacs.conversations.axolotl"); + xmpp_stanza_t *header = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(header, "header"); + char device_id_str[10+1] = {0}; + snprintf(device_id_str, 10+1, "%u", omemo->device_id); + xmpp_stanza_set_attribute(header, "sid", device_id_str); + + int ret, keycount = 0; + signal_int_list *devicelist; + const char *target = jid; + for (int self = 0; self <= 1; self++) + { + if ((ret = dls_load_devicelist(&devicelist, target, omemo))) return NULL; + for (size_t i = 0; i < signal_int_list_size(devicelist); i++) + { + uint32_t device_id = signal_int_list_at(devicelist, i); + signal_protocol_address address = { + .name = target, .name_len = strlen(target), .device_id = device_id}; + + xmpp_stanza_t *header__key = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(header__key, "key"); + char device_id_str[10+1] = {0}; + snprintf(device_id_str, 10+1, "%u", device_id); + xmpp_stanza_set_attribute(header__key, "rid", device_id_str); + + session_builder *builder = NULL; + if (((ret = ss_contains_session_func(&address, omemo))) <= 0) + { + session_pre_key_bundle *bundle; + if ((ret = bks_load_bundle(&bundle, &address, omemo))) continue; + + if ((ret = session_builder_create(&builder, omemo->store_context, &address, omemo->context))) continue; + if ((ret = session_builder_process_pre_key_bundle(builder, bundle))) continue; + } + + session_cipher *cipher; + if ((ret = session_cipher_create(&cipher, omemo->store_context, &address, omemo->context))) continue; + + ciphertext_message *signal_message; + if ((ret = session_cipher_encrypt(cipher, key_and_tag, AES_KEY_SIZE+tag_len, &signal_message))) continue; + signal_buffer *record = ciphertext_message_get_serialized(signal_message); + int prekey = ciphertext_message_get_type(signal_message) == CIPHERTEXT_PREKEY_TYPE + ? 1 : 0; + + char *payload = NULL; + base64_encode(signal_buffer_data(record), signal_buffer_len(record), + &payload); + + if (prekey) + xmpp_stanza_set_attribute(header__key, "prekey", + prekey ? "true" : "false"); + stanza__set_text(account->context, header__key, with_free(payload)); + xmpp_stanza_add_child(header, header__key); + xmpp_stanza_release(header__key); + + keycount++; + + signal_buffer_free(record); + //SIGNAL_UNREF(signal_message); + session_cipher_free(cipher); + if (builder) + session_builder_free(builder); + } + signal_int_list_free(devicelist); + target = account_jid(account); } + free(key_and_tag); + + if (keycount == 0) return NULL; + + xmpp_stanza_t *header__iv = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(header__iv, "iv"); + stanza__set_text(account->context, header__iv, with_noop(iv64)); + xmpp_stanza_add_child(header, header__iv); + xmpp_stanza_release(header__iv); + xmpp_stanza_add_child(encrypted, header); + xmpp_stanza_release(header); + xmpp_stanza_t *encrypted__payload = xmpp_stanza_new(account->context); + xmpp_stanza_set_name(encrypted__payload, "payload"); + stanza__set_text(account->context, encrypted__payload, with_noop(ciphertext64)); + xmpp_stanza_add_child(encrypted, encrypted__payload); + xmpp_stanza_release(encrypted__payload); + + free(iv64); + free(key64); + free(ciphertext64); + return encrypted; } void omemo__free(struct t_omemo *omemo) { if (omemo) { - if (omemo->context) + if (omemo->context) { signal_context_destroy(omemo->context); + signal_protocol_store_context_destroy(omemo->store_context); + } if (omemo->identity) ratchet_identity_key_pair_destroy( (signal_type_base *)omemo->identity); + free(omemo->db_path); free(omemo); } } diff --git a/omemo.h b/omemo.h index 03c29b2..80c9ebe 100644 --- a/omemo.h +++ b/omemo.h @@ -13,20 +13,44 @@ struct t_omemo struct signal_protocol_store_context *store_context; struct t_omemo_db *db; + char *db_path; struct ratchet_identity_key_pair *identity; uint32_t device_id; }; +struct t_omemo_bundle_req +{ + char *id; + char *jid; + char *device; + char *message_text; +}; + +struct t_omemo_devicelist_req +{ + char *id; + struct t_omemo_bundle_req bundle_req; +}; + +xmpp_stanza_t *omemo__get_bundle(xmpp_ctx_t *context, char *from, char *to, + struct t_omemo *omemo); + void omemo__init(struct t_gui_buffer *buffer, struct t_omemo **omemo, const char *account_name); -void omemo__serialize(struct t_omemo *omemo, char **device, - char **identity, size_t *identity_len); +void omemo__handle_devicelist(struct t_omemo *omemo, const char *jid, + xmpp_stanza_t *items); + +void omemo__handle_bundle(struct t_omemo *omemo, const char *jid, + uint32_t device_id, xmpp_stanza_t *items); + +char *omemo__decode(struct t_account *account, const char *jid, + xmpp_stanza_t *encrypted); -void omemo__deserialize(struct t_omemo *omemo, const char *device, - const char *identity, size_t identity_len); +xmpp_stanza_t *omemo__encode(struct t_account *account, const char *jid, + const char *unencrypted); void omemo__free(struct t_omemo *omemo); diff --git a/plugin.h b/plugin.h index 4c6f6d8..6711120 100644 --- a/plugin.h +++ b/plugin.h @@ -5,15 +5,11 @@ #ifndef _WEECHAT_XMPP_PLUGIN_H_ #define _WEECHAT_XMPP_PLUGIN_H_ -#ifndef __cplusplus -#include -#define weechat_plugin weechat_xmpp_plugin() -#define WEECHAT_XMPP_PLUGIN_NAME weechat_xmpp_plugin_name() -#define WEECHAT_XMPP_PLUGIN_VERSION weechat_xmpp_plugin_version() -#endif//__cplusplus +#define weechat_plugin weechat_xmpp_plugin +#define WEECHAT_XMPP_PLUGIN_NAME "xmpp" +#define WEECHAT_XMPP_PLUGIN_VERSION "0.1.1" +#define TIMER_INTERVAL_SEC 0.01 -struct t_weechat_plugin *weechat_xmpp_plugin(); -const char *weechat_xmpp_plugin_name(); -const char *weechat_xmpp_plugin_version(); +extern struct t_weechat_plugin *weechat_xmpp_plugin; #endif /*WEECHAT_XMPP_PLUGIN_H*/ diff --git a/user.c b/user.c index 3401553..a55e36d 100644 --- a/user.c +++ b/user.c @@ -3,6 +3,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include #include @@ -140,7 +141,7 @@ void user__nicklist_remove(struct t_account *account, ptr_buffer = channel ? channel->buffer : account->buffer; - if ((ptr_nick = weechat_nicklist_search_nick(ptr_buffer, NULL, name))) + if (name && (ptr_nick = weechat_nicklist_search_nick(ptr_buffer, NULL, name))) weechat_nicklist_remove_nick(ptr_buffer, ptr_nick); } @@ -190,6 +191,7 @@ struct t_user *user__new(struct t_account *account, new_user->profile.email = NULL; new_user->profile.role = NULL; new_user->profile.pgp_id = NULL; + new_user->profile.omemo = 0; new_user->updated = 0; new_user->is_away = 0; diff --git a/user.h b/user.h index 9c153c5..ccc9537 100644 --- a/user.h +++ b/user.h @@ -16,6 +16,7 @@ struct t_user_profile char *role; char *affiliation; char *pgp_id; + int omemo; }; struct t_user diff --git a/xmpp/iq.c b/xmpp/iq.c index 91c70e5..f5d14a6 100644 --- a/xmpp/iq.c +++ b/xmpp/iq.c @@ -98,9 +98,36 @@ xmpp_stanza_t *stanza__iq_pubsub_items(xmpp_ctx_t *context, xmpp_stanza_t *base, if (node) { xmpp_stanza_set_attribute(parent, "node", node); + } + + return parent; +} + +xmpp_stanza_t *stanza__iq_pubsub_subscribe(xmpp_ctx_t *context, xmpp_stanza_t *base, + struct t_string *node, struct t_string *jid) +{ + xmpp_stanza_t *parent = base; + + if (!parent) + { + parent = xmpp_stanza_new(context); + xmpp_stanza_set_name(parent, "subscribe"); + } + + if (node) + { + xmpp_stanza_set_attribute(parent, "node", node->value); + node->finalize(node); free(node); } + if (jid) + { + xmpp_stanza_set_attribute(parent, "jid", jid->value); + jid->finalize(jid); + free(jid); + } + return parent; } @@ -193,7 +220,7 @@ xmpp_stanza_t *stanza__iq_pubsub_publish_item_list(xmpp_ctx_t *context, xmpp_sta } xmpp_stanza_t *stanza__iq_pubsub_publish_item_list_device(xmpp_ctx_t *context, xmpp_stanza_t *base, - struct t_string *id) + struct t_string *id, struct t_string *label) { xmpp_stanza_t *parent = base; @@ -210,6 +237,13 @@ xmpp_stanza_t *stanza__iq_pubsub_publish_item_list_device(xmpp_ctx_t *context, x free(id); } + if (label) + { + xmpp_stanza_set_attribute(parent, "label", label->value); + label->finalize(label); + free(label); + } + return parent; } @@ -401,3 +435,31 @@ xmpp_stanza_t *stanza__iq_ping(xmpp_ctx_t *context, xmpp_stanza_t *base, return parent; } + +xmpp_stanza_t *stanza__iq_query(xmpp_ctx_t *context, xmpp_stanza_t *base, + struct t_string *ns, struct t_string *node) +{ + xmpp_stanza_t *parent = base; + + if (!parent) + { + parent = xmpp_stanza_new(context); + xmpp_stanza_set_name(parent, "query"); + } + + if (ns) + { + xmpp_stanza_set_ns(parent, ns->value); + ns->finalize(ns); + free(ns); + } + + if (node) + { + xmpp_stanza_set_attribute(parent, "node", node->value); + node->finalize(node); + free(node); + } + + return parent; +} diff --git a/xmpp/stanza.h b/xmpp/stanza.h index e63f7de..f893ab5 100644 --- a/xmpp/stanza.h +++ b/xmpp/stanza.h @@ -49,6 +49,22 @@ static inline struct t_string *with_xmpp_free(char *value, xmpp_ctx_t *pointer) return string; } +static inline void stanza__set_text(xmpp_ctx_t *context, xmpp_stanza_t *parent, + struct t_string *value) +{ + xmpp_stanza_t *text = xmpp_stanza_new(context); + + if (value) + { + xmpp_stanza_set_text(text, value->value); + xmpp_stanza_add_child(parent, text); + value->finalize(value); + free(value); + } + + xmpp_stanza_release(text); +} + xmpp_stanza_t *stanza__presence(xmpp_ctx_t *context, xmpp_stanza_t *base, xmpp_stanza_t **children, const char *ns, char *from, char *to, const char *type); @@ -62,6 +78,9 @@ xmpp_stanza_t *stanza__iq_pubsub(xmpp_ctx_t *context, xmpp_stanza_t *base, xmpp_stanza_t *stanza__iq_pubsub_items(xmpp_ctx_t *context, xmpp_stanza_t *base, char *node); +xmpp_stanza_t *stanza__iq_pubsub_subscribe(xmpp_ctx_t *context, xmpp_stanza_t *base, + struct t_string *node, struct t_string *jid); + xmpp_stanza_t *stanza__iq_pubsub_publish(xmpp_ctx_t *context, xmpp_stanza_t *base, xmpp_stanza_t **children, struct t_string *node); @@ -72,7 +91,7 @@ xmpp_stanza_t *stanza__iq_pubsub_publish_item_list(xmpp_ctx_t *context, xmpp_sta xmpp_stanza_t **children, struct t_string *ns); xmpp_stanza_t *stanza__iq_pubsub_publish_item_list_device(xmpp_ctx_t *context, xmpp_stanza_t *base, - struct t_string *id); + struct t_string *id, struct t_string *label); xmpp_stanza_t *stanza__iq_pubsub_publish_item_bundle(xmpp_ctx_t *context, xmpp_stanza_t *base, xmpp_stanza_t **children, struct t_string *ns); @@ -98,4 +117,7 @@ xmpp_stanza_t *stanza__iq_enable(xmpp_ctx_t *context, xmpp_stanza_t *base, xmpp_stanza_t *stanza__iq_ping(xmpp_ctx_t *context, xmpp_stanza_t *base, struct t_string *ns); +xmpp_stanza_t *stanza__iq_query(xmpp_ctx_t *context, xmpp_stanza_t *base, + struct t_string *ns, struct t_string *node); + #endif /*WEECHAT_XMPP_STANZA_H*/