From e8d4600ef1d7395aa92821c707dc064d749e6025 Mon Sep 17 00:00:00 2001 From: Tony Olagbaiye Date: Tue, 25 Jan 2022 06:28:46 +0000 Subject: [PATCH] checkpoint --- .envrc | 2 +- account.cpp | 51 +++++--- account.hh | 1 + channel.cpp | 16 ++- connection.cpp | 290 +++++++++++++++++++++------------------------- makefile | 30 +++-- omemo.cpp | 13 ++- plugin.cpp | 9 ++ tests/main.cc | 2 + tests/plugin.inl | 14 +-- xmpp/node.cpp | 67 ++++++----- xmpp/node.hh | 63 +++++++--- xmpp/xep-0027.inl | 33 ++++-- xmpp/xep-0045.inl | 187 ++++++++++++++++++++++++++++-- xmpp/xep-0115.inl | 35 +++++- xmpp/xep-0319.inl | 33 ++++-- 16 files changed, 557 insertions(+), 289 deletions(-) diff --git a/.envrc b/.envrc index b4ec2e7..3602c3a 100644 --- a/.envrc +++ b/.envrc @@ -30,7 +30,7 @@ PACKAGES=( libsignal-protocol-c -l libomemo.scm # Dep (libsignal) lmdb lmdbxx # Dep (lmdb) rnp # Dep (rnpgp) - xsd # Generate xml schema code + xsd # Generate XML schema code ) echo direnv: fetching source - weechat diff --git a/account.cpp b/account.cpp index c885b27..2522e41 100644 --- a/account.cpp +++ b/account.cpp @@ -408,6 +408,7 @@ struct t_account *account__alloc(const char *name) /* alloc memory for new account */ new_account = new struct t_account; + std::memset(&new_account->omemo, 0, sizeof(new_account->omemo)); if (!new_account) { weechat_printf(NULL, @@ -440,7 +441,17 @@ struct t_account *account__alloc(const char *name) new_account->logger.handler = &account__log_emit_weechat; new_account->logger.userdata = new_account; - new_account->context = xmpp_ctx_new(NULL, &new_account->logger); + new_account->memory.alloc = [](const size_t size, void *const) { + return calloc(1, size); + }; + new_account->memory.free = [](void *ptr, void *const) { + free(ptr); + }; + new_account->memory.realloc = [](void *ptr, const size_t size, void *const) { + return realloc(ptr, size); + }; + new_account->memory.userdata = new_account; + new_account->context = xmpp_ctx_new(&new_account->memory, &new_account->logger); new_account->connection = NULL; new_account->buffer = NULL; @@ -742,24 +753,32 @@ int account__timer_cb(const void *pointer, void *data, int remaining_calls) (void) data; (void) remaining_calls; - struct t_account *ptr_account; + try + { + struct t_account *ptr_account; - if (!accounts) return WEECHAT_RC_ERROR; + if (!accounts) return WEECHAT_RC_ERROR; - for (ptr_account = accounts; ptr_account; - ptr_account = ptr_account ? ptr_account->next_account : NULL) - { - if (ptr_account->is_connected - && (xmpp_conn_is_connecting(ptr_account->connection) - || xmpp_conn_is_connected(ptr_account->connection))) - connection__process(ptr_account->context, ptr_account->connection, 10); - else if (ptr_account->disconnected); - else if (ptr_account->reconnect_start > 0 - && ptr_account->reconnect_start < time(NULL)) + for (ptr_account = accounts; ptr_account; + ptr_account = ptr_account ? ptr_account->next_account : NULL) { - account__connect(ptr_account); + if (ptr_account->is_connected + && (xmpp_conn_is_connecting(ptr_account->connection) + || xmpp_conn_is_connected(ptr_account->connection))) + connection__process(ptr_account->context, ptr_account->connection, 10); + else if (ptr_account->disconnected); + else if (ptr_account->reconnect_start > 0 + && ptr_account->reconnect_start < time(NULL)) + { + account__connect(ptr_account); + } } - } - return WEECHAT_RC_OK; + return WEECHAT_RC_OK; + } + catch (const std::exception& ex) + { + __asm__("int3"); + return WEECHAT_RC_ERROR; + } } diff --git a/account.hh b/account.hh index 28302d6..7861508 100644 --- a/account.hh +++ b/account.hh @@ -107,6 +107,7 @@ struct t_account int reconnect_delay; int reconnect_start; + xmpp_mem_t memory; xmpp_log_t logger; xmpp_ctx_t *context; xmpp_conn_t *connection; diff --git a/channel.cpp b/channel.cpp index e4139a7..985a125 100644 --- a/channel.cpp +++ b/channel.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -152,8 +153,8 @@ 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_remote_jid; - char buffer_name[1024]; + const char *short_name = NULL, *localvar_remote_jid = NULL; + char buffer_name[1024] = {0}; buffer_created = 0; @@ -1114,9 +1115,14 @@ int channel__send_message(struct t_account *account, struct t_channel *channel, channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 0); } - char *url = (char*)strstr(body, "http"); - if (url && channel->transport == CHANNEL_TRANSPORT_PLAIN) + static const std::regex pattern("https?:[^ ]*"); + std::cmatch match; + if (channel->transport == CHANNEL_TRANSPORT_PLAIN && + std::regex_search(body, match, pattern) + && match[0].matched && !match.prefix().length()) { + std::string url { &*match[0].first, static_cast(match[0].length()) }; + 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:oob"); @@ -1125,7 +1131,7 @@ int channel__send_message(struct t_account *account, struct t_channel *channel, xmpp_stanza_set_name(message__x__url, "url"); xmpp_stanza_t *message__x__url__text = xmpp_stanza_new(account->context); - xmpp_stanza_set_text(message__x__url__text, url); + xmpp_stanza_set_text(message__x__url__text, url.data()); xmpp_stanza_add_child(message__x__url, message__x__url__text); xmpp_stanza_release(message__x__url__text); diff --git a/connection.cpp b/connection.cpp index 11f90fb..2da97c9 100644 --- a/connection.cpp +++ b/connection.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include @@ -85,188 +87,164 @@ int connection__version_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void * int connection__presence_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userdata) { - struct t_account *account = (struct t_account *)userdata; + struct t_account *account = reinterpret_cast(userdata); struct t_user *user; struct t_channel *channel; - 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; auto binding = xml::presence(account->context, stanza); - to = xmpp_stanza_get_to(stanza); - from = xmpp_stanza_get_from(stanza); - if (from == NULL) + if (!binding.from) return 1; - from_bare = xmpp_jid_bare(account->context, from); - from_res = xmpp_jid_resource(account->context, from); - type = xmpp_stanza_get_type(stanza); - show = xmpp_stanza_get_child_by_name(stanza, "show"); - 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; - pres__x_signed = xmpp_stanza_get_child_by_name_and_ns( - stanza, "x", "jabber:x:signed"); - if (pres__x_signed) - { - certificate = xmpp_stanza_get_text(pres__x_signed); - } - pres__c = xmpp_stanza_get_child_by_name_and_ns( - stanza, "c", "http://jabber.org/protocol/caps"); - if (pres__c) + + std::string clientid; + if (auto caps = binding.capabilities()) { - 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) + 1; - clientid = (char*)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, - const_cast("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]); - } + auto node = caps->node; + auto ver = caps->verification; + + clientid = fmt::format("{}#{}", node, ver); + + std::string to = binding.to ? binding.to->full : ""; + + xmpp_stanza_t *children[2] = {NULL}; + children[0] = stanza__iq_pubsub_items(account->context, NULL, + const_cast("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.size() ? strdup(to.data()) : NULL, + binding.from->bare.size() ? strdup(binding.from->bare.data()) : NULL, strdup("get")); + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); } - 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") != 0 && !pres__x_muc_user && !channel) - channel = channel__new(account, CHANNEL_TYPE_PM, from_bare, from_bare); - - 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)) + + channel = channel__search(account, std::string(binding.from->bare).data()); + if (!(binding.type && *binding.type == "unavailable") && !binding.muc_user() && !channel) + channel = channel__new(account, CHANNEL_TYPE_PM, std::string(binding.from->bare).data(), std::string(binding.from->bare).data()); + + if (auto x = binding.muc_user()) { - const char *pres__x__item__name = xmpp_stanza_get_name(pres__x__item); - if (weechat_strcasecmp(pres__x__item__name, "item") != 0) + for (int& status : x->statuses) { - if (weechat_strcasecmp(pres__x__item__name, "status") == 0) + switch (status) { - 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 + case 100: // Non-Anonymous: [message | Entering a room]: Inform user that any occupant is allowed to see the user's full JID + if (channel) 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; - } + 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(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) - user = user__new(account, from, - channel && weechat_strcasecmp(from_bare, channel->id) == 0 - ? from_res : from); - user->profile.status_text = status ? strdup(status) : NULL; - user->profile.status = show ? strdup(show__text) : NULL; - user->profile.idle = idle ? strdup(idle__since) : NULL; - user->is_away = show ? weechat_strcasecmp(show__text, "away") == 0 : 0; - user->profile.role = role ? strdup(role) : NULL; - user->profile.affiliation = affiliation && strcmp(affiliation, "none") != 0 - ? strdup(affiliation) : NULL; - if (certificate && channel) + for (auto& item : x->items) { - user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); - if (channel->type != CHANNEL_TYPE_MUC) - channel->pgp.id = user->profile.pgp_id; - } + using xml::xep0045; - if (channel) - { - if (weechat_strcasecmp(role, "none") == 0) - channel__remove_member(account, channel, from, status); - else - channel__add_member(account, channel, from, jid ? jid : clientid); + std::string role(item.role ? xep0045::format_role(*item.role) : ""); + std::string affiliation(item.affiliation ? xep0045::format_affiliation(*item.affiliation) : ""); + std::string jid = item.target ? item.target->full : clientid; + + user = user__search(account, std::string(binding.from->full).data()); + if (!user) + user = user__new(account, std::string(binding.from->full).data(), + channel && std::string(binding.from->bare).data() == channel->id + ? (binding.from->resource.size() ? std::string(binding.from->resource).data() : "") + : std::string(binding.from->full).data()); + auto status = binding.status(); + auto show = binding.show(); + auto idle = binding.idle_since(); + user->profile.status_text = status ? strdup(status->data()) : NULL; + user->profile.status = show ? strdup(show->data()) : NULL; + user->profile.idle = idle ? strdup("2000-01-01T00:00:00.000z") : NULL; + user->is_away = show ? *show == "away" : false; + user->profile.role = role.size() ? strdup(role.data()) : NULL; + user->profile.affiliation = affiliation.size() && affiliation == "none" + ? strdup(affiliation.data()) : NULL; + if (channel) + { + if (auto signature = binding.signature()) + { + user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, signature->data()); + if (channel->type != CHANNEL_TYPE_MUC) + channel->pgp.id = user->profile.pgp_id; + } + + if (weechat_strcasecmp(role.data(), "none") == 0) + channel__remove_member(account, channel, std::string(binding.from->full).data(), status ? status->data() : nullptr); + else + channel__add_member(account, channel, std::string(binding.from->full).data(), jid.data()); + } } } else { - user = user__search(account, from); + user = user__search(account, std::string(binding.from->full).data()); if (!user) - user = user__new(account, from, - channel && weechat_strcasecmp(from_bare, channel->id) == 0 - ? from_res : from); - user->profile.status_text = status ? strdup(status) : NULL; - user->profile.status = show ? strdup(show__text) : NULL; - user->profile.idle = idle ? strdup(idle__since) : NULL; - user->is_away = show ? weechat_strcasecmp(show__text, "away") == 0 : 0; - user->profile.role = role ? strdup(role) : NULL; - user->profile.affiliation = affiliation && strcmp(affiliation, "none") != 0 - ? strdup(affiliation) : NULL; - if (certificate && channel) - { - user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, certificate); - if (channel->type != CHANNEL_TYPE_MUC) - channel->pgp.id = user->profile.pgp_id; - } - + user = user__new(account, std::string(binding.from->full).data(), + channel && std::string(binding.from->bare).data() == channel->id + ? (binding.from->resource.size() ? std::string(binding.from->resource).data() : "") + : std::string(binding.from->full).data()); + auto status = binding.status(); + auto show = binding.show(); + auto idle = binding.idle_since(); + user->profile.status_text = status ? strdup(status->data()) : NULL; + user->profile.status = show ? strdup(show->data()) : NULL; + user->profile.idle = idle ? strdup("2000-01-01T00:00:00.000z") : NULL; + user->is_away = show ? *show == "away" : false; + user->profile.role = NULL; + user->profile.affiliation = NULL; if (channel) { - if (weechat_strcasecmp(type, "unavailable") == 0) - channel__remove_member(account, channel, from, status); + if (auto signature = binding.signature(); signature && account->pgp) + { + user->profile.pgp_id = pgp__verify(channel->buffer, account->pgp, signature->data()); + if (channel->type != CHANNEL_TYPE_MUC) + channel->pgp.id = user->profile.pgp_id; + } + + if (user->profile.role) + channel__remove_member(account, channel, std::string(binding.from->full).data(), status ? status->data() : nullptr); else - channel__add_member(account, channel, from, clientid); + channel__add_member(account, channel, std::string(binding.from->full).data(), clientid.data()); } } - if (clientid) - free(clientid); - return 1; } @@ -1213,8 +1191,6 @@ void connection__handler(xmpp_conn_t *conn, xmpp_conn_event_t status, NULL, "presence", NULL, account); xmpp_handler_add(conn, &connection__message_handler, NULL, "message", /*type*/ NULL, account); - //xmpp_handler_add(conn, &connection__iq_handler, - // NULL, "iq", "get", account); xmpp_handler_add(conn, &connection__iq_handler, NULL, "iq", NULL, account); diff --git a/makefile b/makefile index 496bd3f..0597b80 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ ifdef DEBUG - DBGCFLAGS=-fsanitize=address -fsanitize=undefined -fsanitize=leak - DBGLDFLAGS=-lasan -lubsan -llsan + DBGCFLAGS=-fno-omit-frame-pointer -fsanitize=address #-fsanitize=undefined -fsanitize=leak + DBGLDFLAGS=-lasan -lrt -lasan #-lubsan -llsan endif RM=rm -f @@ -8,8 +8,8 @@ FIND=find INCLUDES=-Ilibstrophe -Ideps -Ideps/fmt/include \ $(shell xml2-config --cflags) \ - $(shell pkg-config --cflags librnp-0) \ - $(shell pkg-config --cflags libomemo-c) + $(shell pkg-config --cflags librnp) \ + $(shell pkg-config --cflags libsignal-protocol-c) CFLAGS+=$(DBGCFLAGS) \ -fno-omit-frame-pointer -fPIC \ -fvisibility=hidden -fvisibility-inlines-hidden \ @@ -23,7 +23,7 @@ CFLAGS+=$(DBGCFLAGS) \ CPPFLAGS+=$(DBGCFLAGS) -O0 \ -fno-omit-frame-pointer -fPIC \ -fvisibility=hidden -fvisibility-inlines-hidden \ - -std=c++20 -gdwarf-4 -fkeep-inline-functions \ + -std=c++23 -gdwarf-4 -fkeep-inline-functions \ -Wall -Wextra -pedantic \ -Wno-missing-field-initializers \ $(INCLUDES) @@ -34,8 +34,8 @@ LDFLAGS+=$(DBGLDFLAGS) \ LDLIBS=-lstrophe \ -lpthread \ $(shell xml2-config --libs) \ - $(shell pkg-config --libs librnp-0) \ - $(shell pkg-config --libs libomemo-c) \ + $(shell pkg-config --libs librnp) \ + $(shell pkg-config --libs libsignal-protocol-c) \ -lgcrypt \ -llmdb @@ -92,9 +92,6 @@ weechat-xmpp: $(DEPS) xmpp.so xmpp.so: $(OBJS) $(DEPS) $(HDRS) $(CXX) $(LDFLAGS) -o $@ $(OBJS) $(DEPS) $(LDLIBS) - which patchelf >/dev/null && \ - patchelf --set-rpath $(LIBRARY_PATH):$(shell realpath $(shell dirname $(shell gcc --print-libgcc-file-name))/../../../) xmpp.so && \ - patchelf --shrink-rpath xmpp.so || true .%.o: %.c $(CC) $(CFLAGS) -c $< -o $@ @@ -123,15 +120,14 @@ deps/fmt/libfmt.a: $(MAKE) -C deps/fmt fmt fmt: deps/fmt/libfmt.a -tests/run: $(COVS) $(DEPS) $(HDRS) tests/main.cc +tests/xmpp.cov.so: $(COVS) $(DEPS) $(HDRS) $(CXX) --coverage -O0 $(LDFLAGS) -o tests/xmpp.cov.so $(COVS) $(DEPS) $(LDLIBS) - env --chdir tests $(CXX) $(CPPFLAGS) -o run xmpp.cov.so main.cc $(LDLIBS) - which patchelf >/dev/null && \ - patchelf --set-rpath $(PWD)/tests:$(LIBRARY_PATH):$(shell realpath $(shell dirname $(shell gcc --print-libgcc-file-name))/../../../) tests/xmpp.cov.so tests/run && \ - patchelf --shrink-rpath tests/run tests/xmpp.cov.so || true + +tests/run: $(COVS) tests/main.cc tests/xmpp.cov.so + env --chdir tests $(CXX) $(CPPFLAGS) -o run ./xmpp.cov.so main.cc $(LDLIBS) test: tests/run - env --chdir tests ./run + env --chdir tests ./run -s coverage: tests/run gcov -m -abcfu -rqk -i .*.gcda xmpp/.*.gcda @@ -160,7 +156,7 @@ tidy: $(FIND) . -name "*.gcda" -delete clean: - $(RM) -f $(OBJS) + $(RM) -f $(OBJS) $(COVS) $(MAKE) -C deps/diff clean || true $(MAKE) -C deps/fmt clean || true git submodule foreach --recursive git clean -xfd || true diff --git a/omemo.cpp b/omemo.cpp index 14a7a57..5867375 100644 --- a/omemo.cpp +++ b/omemo.cpp @@ -61,6 +61,18 @@ size_t base64_encode(const uint8_t *buffer, size_t length, char **result) return weechat_string_base_encode(64, (char*)buffer, length, *result); } +std::vector base64_decode(std::string_view buffer) +{ + auto result = std::make_unique(buffer.size() + 1); + return std::vector(result.get(), result.get() + weechat_string_base_decode(64, buffer.data(), (char*)result.get())); +} + +std::string base64_encode(std::vector buffer) +{ + auto result = std::make_unique(buffer.size() * 2); + return std::string(result.get(), result.get() + weechat_string_base_encode(64, (char*)buffer.data(), buffer.size(), result.get())); +} + 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) @@ -1761,7 +1773,6 @@ std::optional bks_load_bundle(struct signal_protocol_ return bundle; } -extern "C" void log_emit_weechat(int level, const char *message, size_t len, void *user_data) { struct t_gui_buffer *buffer = (struct t_gui_buffer*)user_data; diff --git a/plugin.cpp b/plugin.cpp index 9c371ab..2f6a501 100644 --- a/plugin.cpp +++ b/plugin.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -34,12 +35,20 @@ WEECHAT_PLUGIN_LICENSE("MPL2"); WEECHAT_PLUGIN_PRIORITY(5500); } +extern "C" +void weechat_signal_handler(int) +{ + __asm__("int3"); +} + extern "C" int weechat_plugin_init(struct t_weechat_plugin *plugin, int argc, char *argv[]) { (void) argc; (void) argv; + std::signal(SIGSEGV, weechat_signal_handler); + weechat_xmpp_plugin = plugin; if (!config__init()) diff --git a/tests/main.cc b/tests/main.cc index af24eeb..4bd61aa 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -1,3 +1,5 @@ #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include + +#include "plugin.inl" diff --git a/tests/plugin.inl b/tests/plugin.inl index 84778f2..38a6b25 100644 --- a/tests/plugin.inl +++ b/tests/plugin.inl @@ -1,18 +1,14 @@ #include +#include #include "../plugin.hh" -TEST_CASE("placeholder") +TEST_CASE("weechat") { - int argc = 2; - const char *argv[2] = {"a", "b"}; + std::string current("20211106-01"); - SUBCASE("takes no arguments") + SUBCASE("plugin api match") { - CHECK(argc != 1); + CHECK(current == WEECHAT_PLUGIN_API_VERSION); } - - (void) argv; - //weechat::plugin c; - //CHECK(&c.name() == NULL); } diff --git a/xmpp/node.cpp b/xmpp/node.cpp index 6a12754..1782b7c 100644 --- a/xmpp/node.cpp +++ b/xmpp/node.cpp @@ -2,6 +2,8 @@ // 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 "node.hh" #pragma GCC visibility push(default) #include "ns.hh" @@ -36,54 +38,40 @@ std::string get_text(xmpp_stanza_t *stanza) { std::chrono::system_clock::time_point get_time(const std::string& text) { std::tm tm = {}; - std::istringstream ss(text); - //ss.imbue(std::locale("en_GB.utf-8")); - ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S%z"); - if (ss.fail()) { + if (strptime(text.data(), "%FT%T%z", &tm)) { throw std::invalid_argument("Bad time format"); } else { return std::chrono::system_clock::from_time_t(std::mktime(&tm)); } } -jid::jid(xmpp_ctx_t *context, std::string s) { - char *result = NULL; - result = xmpp_jid_node(context, s.data()); - if (result) - { - local = result; - xmpp_free(context, result); - result = NULL; - } - result = xmpp_jid_domain(context, s.data()); - if (result) +const std::regex jid::pattern( + "^((?:([^@/<>'\"]+)@)?([^@/<>'\"]+))(?:/([^<>'\"]*))?$"); + +jid::jid(xmpp_ctx_t *, std::string s) : full(s) { + auto as_sv = [](std::ssub_match m) { + if(!m.matched) return std::string_view(); + return std::string_view { &*m.first, static_cast(m.length()) }; + }; + + std::smatch match; + + if (std::regex_search(full, match, pattern)) { - domain = result; - xmpp_free(context, result); - result = NULL; + bare = as_sv(match[1]); + local = as_sv(match[2]); + domain = as_sv(match[3]); + resource = as_sv(match[4]); } else - throw std::invalid_argument("Invalid JID"); - result = xmpp_jid_resource(context, s.data()); - if (result) { - resource = result; - xmpp_free(context, result); - result = NULL; + bare = full; + domain = bare; } } -std::string jid::full() const { - return fmt::format("{}{}{}{}{}", *local, local ? "@" : "", domain, - resource ? "/" : "", *resource); -} - -std::string jid::bare() const { - return fmt::format("{}{}{}", *local, local ? "@" : "", domain); -} - bool jid::is_bare() const { - return !resource; + return !resource.empty(); } xml::node::node() {} @@ -164,3 +152,14 @@ void xml::iq::bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza) { node::bind(context, stanza); } + +void xml::error::bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza) { + auto result = get_attribute(stanza, "from"); + if (result) + from = jid(context, *result); + result = get_attribute(stanza, "to"); + if (result) + to = jid(context, *result); + + node::bind(context, stanza); +} diff --git a/xmpp/node.hh b/xmpp/node.hh index 05a2681..283f69c 100644 --- a/xmpp/node.hh +++ b/xmpp/node.hh @@ -7,14 +7,12 @@ #include #include #include +#include #include #include #include #include #include -#include -#include -#include #include #include @@ -27,15 +25,20 @@ std::string get_text(xmpp_stanza_t *stanza); std::chrono::system_clock::time_point get_time(const std::string& text); class jid { -public: - std::optional local; - std::string domain; - std::optional resource; +private: + static const std::regex pattern; +public: jid(xmpp_ctx_t *context, std::string s); - std::string full() const; - std::string bare() const; + operator std::string&() { return full; } + + std::string full; + std::string_view bare; + std::string_view local; + std::string_view domain; + std::string_view resource; + bool is_bare() const; }; @@ -46,10 +49,12 @@ namespace xml { explicit node(); public: - inline node(xmpp_ctx_t *context, xmpp_stanza_t *stanza) { + inline node(xmpp_ctx_t *context, xmpp_stanza_t *stanza) : context(context) { bind(context, stanza); } + xmpp_ctx_t *context; + std::optional name; std::optional id; @@ -62,6 +67,14 @@ namespace xml { virtual void bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza); + inline std::optional + get_attr(const std::string& name) { + auto attribute = attributes.find(name); + if (attribute != attributes.end()) + return attribute->second; + return {}; + } + template inline std::vector> get_children(std::string_view name) { @@ -96,9 +109,12 @@ namespace xml { namespace xml { - class message : virtual public node, public xep0027 { + class message : virtual public node, + public xep0027 { public: - using node::node; + inline message(xmpp_ctx_t *context, xmpp_stanza_t *stanza) : node(context, stanza) { + bind(context, stanza); + } std::optional from; std::optional to; @@ -108,9 +124,12 @@ namespace xml { void bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza) override; }; - class presence : virtual public node, public xep0045, public xep0115, public xep0319 { + class presence : virtual public node, + public xep0027, public xep0045, public xep0115, public xep0319 { public: - using node::node; + inline presence(xmpp_ctx_t *context, xmpp_stanza_t *stanza) : node(context, stanza) { + bind(context, stanza); + } std::optional from; std::optional to; @@ -125,7 +144,9 @@ namespace xml { class iq : virtual public node { public: - using node::node; + inline iq(xmpp_ctx_t *context, xmpp_stanza_t *stanza) : node(context, stanza) { + bind(context, stanza); + } std::optional from; std::optional to; @@ -135,4 +156,16 @@ namespace xml { void bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza) override; }; + class error : virtual public node { + public: + inline error(xmpp_ctx_t *context, xmpp_stanza_t *stanza) : node(context, stanza) { + bind(context, stanza); + } + + std::optional from; + std::optional to; + + void bind(xmpp_ctx_t *context, xmpp_stanza_t *stanza) override; + }; + } diff --git a/xmpp/xep-0027.inl b/xmpp/xep-0027.inl index 504f829..3b8dd9d 100644 --- a/xmpp/xep-0027.inl +++ b/xmpp/xep-0027.inl @@ -15,19 +15,32 @@ namespace xml { class xep0027 : virtual public node { + private: + std::optional> _signature; + std::optional> _encrypted; public: - std::optional signature() { - auto child = get_children("x"); - if (child.size() > 0) - return child.front().get().text; - return {}; + std::optional& signature() { + if (!_signature) + { + auto child = get_children("x"); + if (child.size() > 0) + _signature = child.front().get().text; + else + _signature.emplace(std::nullopt); + } + return *_signature; } - std::optional encrypted() { - auto child = get_children("x"); - if (child.size() > 0) - return child.front().get().text; - return {}; + std::optional& encrypted() { + if (!_encrypted) + { + auto child = get_children("x"); + if (child.size() > 0) + _encrypted = child.front().get().text; + else + _encrypted.emplace(std::nullopt); + } + return *_encrypted; } }; diff --git a/xmpp/xep-0045.inl b/xmpp/xep-0045.inl index ae192de..0cafa6f 100644 --- a/xmpp/xep-0045.inl +++ b/xmpp/xep-0045.inl @@ -5,7 +5,10 @@ #pragma once #include +#include #include +#include +#include #include "node.hh" #pragma GCC visibility push(default) @@ -16,31 +19,193 @@ namespace xml { class xep0045 : virtual public node { public: + enum class affiliation { + admin, + member, + none, + outcast, + owner, + }; + + enum class role { + moderator, + none, + participant, + visitor, + }; + + static affiliation parse_affiliation(std::string_view s) { + if (s == "admin") + return affiliation::admin; + else if (s == "member") + return affiliation::member; + else if (s == "none") + return affiliation::none; + else if (s == "outcast") + return affiliation::outcast; + else if (s == "owner") + return affiliation::owner; + throw std::invalid_argument( + fmt::format("Bad affiliation: {}", s)); + } + + static std::string_view format_affiliation(affiliation e) { + switch (e) { + case affiliation::admin: + return "admin"; + case affiliation::member: + return "member"; + case affiliation::none: + return "none"; + case affiliation::outcast: + return "outcast"; + case affiliation::owner: + return "owner"; + default: + return ""; + } + } + + static role parse_role(std::string_view s) { + if (s == "moderator") + return role::moderator; + else if (s == "none") + return role::none; + else if (s == "participant") + return role::participant; + else if (s == "visitor") + return role::visitor; + throw std::invalid_argument( + fmt::format("Bad role: {}", s)); + } + + static std::string_view format_role(role e) { + switch (e) { + case role::moderator: + return "moderator"; + case role::none: + return "none"; + case role::participant: + return "participant"; + case role::visitor: + return "visitor"; + default: + return ""; + } + } + class x { private: struct decline { + decline(node& node) { + for (auto& child : node.get_children("reason")) + reason += child.get().text; + if (auto attr = node.get_attr("from")) + from = jid(node.context, *attr); + if (auto attr = node.get_attr("to")) + to = jid(node.context, *attr); + }; + std::string reason; std::optional from; std::optional to; }; struct destroy { + destroy(node& node) { + for (auto& child : node.get_children("reason")) + reason += child.get().text; + if (auto attr = node.get_attr("target")) + target = jid(node.context, *attr); + }; + std::string reason; std::optional target; }; struct invite { + invite(node& node) { + for (auto& child : node.get_children("reason")) + reason += child.get().text; + if (auto attr = node.get_attr("from")) + from = jid(node.context, *attr); + if (auto attr = node.get_attr("to")) + to = jid(node.context, *attr); + }; + std::string reason; std::optional from; std::optional to; }; - struct item { + class item { + private: + struct actor { + actor(node& node) { + for (auto& child : node.get_children("reason")) + reason += child.get().text; + if (auto attr = node.get_attr("jid")) + target = jid(node.context, *attr); + if (auto attr = node.get_attr("nick")) + nick = *attr; + } + + std::string reason; + std::optional target; + std::string nick; + }; + + struct continue_ { + continue_(node& node) { + if (auto attr = node.get_attr("thread")) + thread = *attr; + } + + std::string thread; + }; + + public: + item(node& node) { + for (auto& child : node.get_children("actor")) + actors.emplace_back(child); + for (auto& child : node.get_children("continue")) + continues.emplace_back(child); + for (auto& child : node.get_children("reason")) + reason += child.get().text; + if (auto attr = node.get_attr("affiliation")) + affiliation = parse_affiliation(*attr); + if (auto attr = node.get_attr("jid")) + target = jid(node.context, *attr); + if (auto attr = node.get_attr("nick")) + nick = *attr; + if (auto attr = node.get_attr("role")) + role = parse_role(*attr); + }; + + std::vector actors; + std::vector continues; std::string reason; + std::optional affiliation; + std::optional target; + std::optional nick; + std::optional role; }; public: - x(const node& node) { + x(node& node) { + for (auto& child : node.get_children("decline")) + declines.emplace_back(child); + for (auto& child : node.get_children("destroy")) + destroys.emplace_back(child); + for (auto& child : node.get_children("invite")) + invites.emplace_back(child); + for (auto& child : node.get_children("item")) + items.emplace_back(child); + for (auto& child : node.get_children("password")) + passwords.emplace_back(child.get().text); + for (auto& child : node.get_children("status")) + if (auto code = child.get().get_attr("code")) + statuses.push_back(std::stoi(*code)); } std::vector declines; @@ -51,11 +216,19 @@ namespace xml { std::vector statuses; }; - std::optional muc_user() { - auto child = get_children("x"); - if (child.size() > 0) - return child.front().get(); - return {}; + private: + std::optional> _muc_user; + public: + std::optional& muc_user() { + if (!_muc_user) + { + auto child = get_children("x"); + if (child.size() > 0) + _muc_user = child.front().get(); + else + _muc_user.emplace(std::nullopt); + } + return *_muc_user; } }; diff --git a/xmpp/xep-0115.inl b/xmpp/xep-0115.inl index 43dbb36..b4b5c14 100644 --- a/xmpp/xep-0115.inl +++ b/xmpp/xep-0115.inl @@ -16,11 +16,36 @@ namespace xml{ class xep0115 : virtual public node { public: - std::optional caps() { - auto child = get_children("c"); - if (child.size() > 0) - return child.front().get(); - return {}; + struct caps { + caps(::xml::node& node) { + if (auto attr = node.get_attr("ext")) + ext = *attr; + if (auto attr = node.get_attr("hash")) + hashalgo = *attr; + if (auto attr = node.get_attr("node")) + this->node = *attr; + if (auto attr = node.get_attr("ver")) + verification = *attr; + }; + + std::optional ext; + std::string hashalgo; + std::string node; + std::string verification; + }; + + private: + std::optional> _capabilities; + public: + std::optional capabilities() { + if (!_capabilities) + { + auto child = get_children("c"); + if (child.size() > 0) + _capabilities = caps(child.front().get()); + _capabilities.emplace(std::nullopt); + } + return *_capabilities; } }; diff --git a/xmpp/xep-0319.inl b/xmpp/xep-0319.inl index ddb961d..462ddcd 100644 --- a/xmpp/xep-0319.inl +++ b/xmpp/xep-0319.inl @@ -17,21 +17,30 @@ namespace xml { class xep0319 : virtual public node { + private: + std::optional> _idle_since; public: std::optional idle_since() { - auto children = get_children("idle"); - if (children.size() <= 0) - return {}; - auto child = children.front().get(); - auto since = child.attributes.find("since"); - if (since == child.attributes.end()) - return {}; - try { - return get_time(since->second); - } - catch (const std::invalid_argument& ex) { - return {}; + if (!_idle_since) + { + auto children = get_children("idle"); + if (children.size() <= 0) + _idle_since.emplace(std::nullopt); + else { + auto since = children.front().get().get_attr("since"); + if (!since) + _idle_since.emplace(std::nullopt); + else { + try { + _idle_since = get_time(*since); + } + catch (const std::invalid_argument& ex) { + _idle_since.emplace(std::nullopt); + } + } + } } + return *_idle_since; } };