From bef4b0a6699f53ff1c73a3dfdd6997967913e12b Mon Sep 17 00:00:00 2001 From: defanor Date: Tue, 17 Nov 2020 19:10:46 +0300 Subject: Support XEP-0163 (PEP) and XEP-0172 (User Nickname) --- README | 8 ++- src/rexmpp.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- src/rexmpp.h | 12 +++++ 3 files changed, 166 insertions(+), 14 deletions(-) diff --git a/README b/README index f8c38de..47de5e7 100644 --- a/README +++ b/README @@ -60,7 +60,8 @@ A rough roadmap: versioning and caching) [+] XEP-0030: Service Discovery (replying to queries) [+] XEP-0115: Entity Capabilities (including into initial presence) -[ ] OpenPGP (XEP-0373) +[+] XEP-0172: User Nickname +[ ] XEP-0373: OpenPGP for XMPP [ ] XEP-0402: PEP Native Bookmarks (autojoin conferences) [ ] XEP-0166: Jingle [ ] XEP-0234: Jingle File Transfer @@ -75,6 +76,7 @@ A rough roadmap: - Additional state tracking: [+] XMPP IM (RFC 6121): track presences of contacts. +[+] XEP-0163: Personal Eventing Protocol: track contacts' published items. [ ] XEP-0030: Service Discovery: track features provided by known entities. [ ] XEP-0115: Entity Capabilities: maintain a capability database, @@ -82,7 +84,9 @@ A rough roadmap: [ ] XEP-0045: Multi-User Chat: tracking of related states/presences. -- Various utility functions? +- Various utility functions: + +[+] Display name establishment. - Examples and application: diff --git a/src/rexmpp.c b/src/rexmpp.c index b0d8d94..a52f743 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -177,6 +177,82 @@ char *rexmpp_capabilities_hash (rexmpp_t *s, return out; } +xmlNodePtr rexmpp_find_event (rexmpp_t *s, + const char *from, + const char *node, + xmlNodePtr *prev_event) +{ + xmlNodePtr prev, cur; + for (prev = NULL, cur = s->roster_events; + cur != NULL; + prev = cur, cur = xmlNextElementSibling(cur)) { + char *cur_from = xmlGetProp(cur, "from"); + xmlNodePtr cur_event = + rexmpp_xml_find_child(cur, + "http://jabber.org/protocol/pubsub#event", + "event"); + xmlNodePtr cur_items = + rexmpp_xml_find_child(cur_event, + "http://jabber.org/protocol/pubsub#event", + "items"); + char *cur_node = xmlGetProp(cur_items, "node"); + int match = (strcmp(cur_from, from) == 0 && strcmp(cur_node, node) == 0); + free(cur_node); + free(cur_from); + if (match) { + if (prev_event != NULL) { + *prev_event = prev; + } + return cur; + } + } + return NULL; +} + +char *rexmpp_get_name (rexmpp_t *s, const char *jid_str) { + struct rexmpp_jid jid; + if (rexmpp_jid_parse(jid_str, &jid) != 0) { + return NULL; + } + if (s->manage_roster) { + xmlNodePtr roster_item = rexmpp_roster_find_item(s, jid.bare, NULL); + if (roster_item) { + char *name = xmlGetProp(roster_item, "name"); + if (name != NULL) { + return name; + } + } + if (s->track_roster_events) { + xmlNodePtr elem = + rexmpp_find_event(s, jid.bare, "http://jabber.org/protocol/nick", NULL); + if (elem != NULL) { + xmlNodePtr event = + rexmpp_xml_find_child(elem, + "http://jabber.org/protocol/pubsub#event", + "event"); + xmlNodePtr items = + rexmpp_xml_find_child(event, + "http://jabber.org/protocol/pubsub#event", + "items"); + xmlNodePtr item = + rexmpp_xml_find_child(items, + "http://jabber.org/protocol/pubsub#event", + "item"); + if (item != NULL) { + xmlNodePtr nick = + rexmpp_xml_find_child(item, + "http://jabber.org/protocol/nick", + "nick"); + if (nick != NULL) { + return strdup(xmlNodeGetContent(nick)); + } + } + } + } + } + return strdup(jid.bare); +} + xmlNodePtr rexmpp_xml_feature (const char *var) { xmlNodePtr feature = xmlNewNode(NULL, "feature"); xmlNewProp(feature, "var", var); @@ -192,20 +268,28 @@ xmlNodePtr rexmpp_xml_error (const char *type, const char *condition) { return error; } -xmlNodePtr rexmpp_xml_default_disco_info () { +xmlNodePtr rexmpp_xml_disco_info (rexmpp_t *s) { + xmlNodePtr first, prev = NULL, cur; /* There must be at least one identity, so filling in somewhat sensible defaults. A basic client may leave them be, while an advanced one would adjust and/or extend them. */ - xmlNodePtr identity = xmlNewNode(NULL, "identity"); - xmlNewProp(identity, "category", "client"); - xmlNewProp(identity, "type", "console"); - xmlNewProp(identity, "name", "rexmpp"); - xmlNodePtr disco_feature = - rexmpp_xml_feature("http://jabber.org/protocol/disco#info"); - identity->next = disco_feature; - xmlNodePtr ping_feature = rexmpp_xml_feature("urn:xmpp:ping"); - disco_feature->next = ping_feature; - return identity; + first = xmlNewNode(NULL, "identity"); + xmlNewProp(first, "category", "client"); + xmlNewProp(first, "type", "console"); + xmlNewProp(first, "name", "rexmpp2"); + prev = first; + cur = rexmpp_xml_feature("http://jabber.org/protocol/disco#info"); + prev->next = cur; + prev = cur; + if (s->nick_notifications) { + cur = rexmpp_xml_feature("http://jabber.org/protocol/nick+notify"); + prev->next = cur; + prev = cur; + } + cur = rexmpp_xml_feature("urn:xmpp:ping"); + prev->next = cur; + prev = cur; + return first; } int rexmpp_sasl_cb (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { @@ -244,6 +328,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->manage_roster = 1; s->roster_cache_file = NULL; s->track_roster_presence = 1; + s->track_roster_events = 1; + s->nick_notifications = 1; s->send_buffer = NULL; s->send_queue = NULL; s->resolver_ctx = NULL; @@ -260,6 +346,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->roster_items = NULL; s->roster_ver = NULL; s->roster_presence = NULL; + s->roster_events = NULL; s->stanza_queue = NULL; s->stream_id = NULL; s->active_iq = NULL; @@ -349,7 +436,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) gsasl_callback_hook_set(s->sasl_ctx, s); gsasl_callback_set(s->sasl_ctx, rexmpp_sasl_cb); - s->disco_info = rexmpp_xml_default_disco_info(); + s->disco_info = rexmpp_xml_disco_info(s); return REXMPP_SUCCESS; } @@ -438,6 +525,10 @@ void rexmpp_done (rexmpp_t *s) { xmlFreeNodeList(s->roster_presence); s->roster_presence = NULL; } + if (s->roster_events != NULL) { + xmlFreeNodeList(s->roster_events); + s->roster_events = NULL; + } if (s->roster_ver != NULL) { free(s->roster_ver); s->roster_ver = NULL; @@ -1635,6 +1726,51 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } } + /* Incoming messages. */ + if (rexmpp_xml_match(elem, "jabber:client", "message")) { + char *from = xmlGetProp(elem, "from"); + if (from != NULL) { + struct rexmpp_jid from_jid; + rexmpp_jid_parse(from, &from_jid); + xmlFree(from); + if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL || + strcmp(from_jid.bare, s->assigned_jid.bare) == 0) { + xmlNodePtr event = + rexmpp_xml_find_child(elem, + "http://jabber.org/protocol/pubsub#event", + "event"); + if (event != NULL && s->manage_roster && s->track_roster_events) { + xmlNodePtr items = + rexmpp_xml_find_child(event, + "http://jabber.org/protocol/pubsub#event", + "items"); + if (items != NULL) { + char *node = xmlGetProp(items, "node"); + if (node != NULL) { + /* Remove the previously stored items for the same sender + and node, if any. */ + xmlNodePtr prev, cur; + cur = rexmpp_find_event(s, from_jid.bare, node, &prev); + if (cur) { + if (prev == NULL) { + s->roster_events = cur->next; + } else { + prev->next = cur->next; + } + } + free(node); + + /* Add the new message. */ + xmlNodePtr message = xmlCopyNode(elem, 1); + message->next = s->roster_events; + s->roster_events = message; + } + } + } + } + } + } + /* Stream negotiation, https://tools.ietf.org/html/rfc6120#section-4.3 */ if (s->stream_state == REXMPP_STREAM_NEGOTIATION && diff --git a/src/rexmpp.h b/src/rexmpp.h index 8b5632a..53f2cba 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -241,6 +241,8 @@ struct rexmpp int manage_roster; const char *roster_cache_file; int track_roster_presence; + int track_roster_events; + int nick_notifications; /* Resource limits. */ uint32_t stanza_queue_size; @@ -260,6 +262,7 @@ struct rexmpp xmlNodePtr roster_items; char *roster_ver; xmlNodePtr roster_presence; + xmlNodePtr roster_events; /* Other dynamic data. */ xmlNodePtr disco_info; @@ -444,6 +447,15 @@ xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node); void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...); +/** + @brief Gets an appropriate display name for a JID. + @param[in] s ::rexmpp + @param[in] jid_str A JID string. + @returns A newly allocated null-terminated string, or NULL on + error. +*/ +char *rexmpp_get_name (rexmpp_t *s, const char *jid_str); + /** @brief Matches an XML node against a namespace and an element name. @param[in] node An XML node to match. -- cgit v1.2.3