summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2020-03-25 18:54:06 +0300
committerdefanor <defanor@uberspace.net>2020-03-25 19:13:31 +0300
commit6e1b2af71d8b281042481c8b32e03bb13538f446 (patch)
treecb5a37522543fb2b55c7c9b7957ee5ccbe6b7813
parent7b3d46b995bc7cc8ac02533822cadaa3e3b4458b (diff)
Add initial roster management functionality
-rw-r--r--README10
-rw-r--r--src/rexmpp.c197
-rw-r--r--src/rexmpp.h16
3 files changed, 205 insertions, 18 deletions
diff --git a/README b/README
index 3edf39d..badd92e 100644
--- a/README
+++ b/README
@@ -31,9 +31,7 @@ A rough roadmap:
[+] XEP-0198: Stream Management. Implemented (both acknowledgements
and resumption, making use of XEP-0203: Delayed Delivery).
-[+] XEP-0280: Message Carbons. Enables them in case if they are
- supported by the server (discovering it using XEP-0030: Service
- Discovery). Though maybe it shouldn't be done by the library.
+[+] XEP-0280: Message Carbons.
- Better connectivity:
@@ -58,9 +56,9 @@ A rough roadmap:
- Primary IM features (?):
-[ ] XMPP IM (RFC 6121): loading and managing of the roster and
- presence subscriptions? Maybe it'd be better done by a client. Or
- just some utility functions can be provided.
+[.] XMPP IM (RFC 6121): loading and managing of the roster and
+ presence subscriptions. Optional roster management (loading and
+ updates on roster push, with versioning) is implemented.
- Common and reliable IM features (?):
diff --git a/src/rexmpp.c b/src/rexmpp.c
index f57c611..7edcb1f 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -81,6 +81,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->server_host = NULL;
s->enable_carbons = 1;
s->enable_service_discovery = 1;
+ s->manage_roster = 1;
s->send_buffer = NULL;
s->send_queue = NULL;
s->server_srv = NULL;
@@ -91,6 +92,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->current_element_root = NULL;
s->current_element = NULL;
s->stream_features = NULL;
+ s->roster_items = NULL;
+ s->roster_ver = NULL;
s->stanza_queue = NULL;
s->stream_id = NULL;
s->active_iq = NULL;
@@ -241,6 +244,14 @@ void rexmpp_done (rexmpp_t *s) {
free(s->stream_id);
s->stream_id = NULL;
}
+ if (s->roster_items != NULL) {
+ xmlFreeNodeList(s->roster_items);
+ s->roster_items = NULL;
+ }
+ if (s->roster_ver != NULL) {
+ free(s->roster_ver);
+ s->roster_ver = NULL;
+ }
while (s->stanza_queue != NULL) {
xmlNodePtr next = xmlNextElementSibling(s->stanza_queue);
xmlFreeNode(s->send_queue);
@@ -550,6 +561,32 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
return ret;
}
+void rexmpp_iq_reply (rexmpp_t *s,
+ xmlNodePtr req,
+ const char *type,
+ xmlNodePtr payload)
+{
+ xmlNodePtr iq_stanza = xmlNewNode(NULL, "iq");
+ xmlNewNs(iq_stanza, "jabber:client", NULL);
+ xmlNewProp(iq_stanza, "type", type);
+ char *id = xmlGetProp(req, "id");
+ if (id != NULL) {
+ xmlNewProp(iq_stanza, "id", id);
+ free(id);
+ }
+ char *to = xmlGetProp(req, "from");
+ if (to != NULL) {
+ xmlNewProp(iq_stanza, "to", to);
+ free(to);
+ }
+ if (s->assigned_jid != NULL) {
+ xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ }
+ if (payload != NULL) {
+ xmlAddChild(iq_stanza, payload);
+ }
+ rexmpp_send(s, iq_stanza);
+}
void rexmpp_iq_new (rexmpp_t *s,
const char *type,
@@ -568,7 +605,7 @@ void rexmpp_iq_new (rexmpp_t *s,
"The IQ queue limit is reached, giving up on the oldest IQ.");
prev->next = NULL;
if (last->cb != NULL) {
- last->cb(s, last->request, NULL);
+ last->cb(s, last->request, NULL, 0);
}
xmlFreeNode(last->request);
free(last);
@@ -996,19 +1033,29 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) {
}
}
-void rexmpp_carbons_enabled (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
- char *type = xmlGetProp(response, "type");
- if (strcmp(type, "result") == 0) {
+void rexmpp_carbons_enabled (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (success) {
rexmpp_log(s, LOG_INFO, "carbons enabled");
s->carbons_state = REXMPP_CARBONS_ACTIVE;
} else {
rexmpp_log(s, LOG_WARNING, "failed to enable carbons");
s->carbons_state = REXMPP_CARBONS_INACTIVE;
}
- free(type);
}
-void rexmpp_discovery_info (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
+void rexmpp_iq_discovery_info (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Failed to discover features");
+ return;
+ }
xmlNodePtr query = xmlFirstElementChild(response);
if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info",
"query")) {
@@ -1034,6 +1081,102 @@ void rexmpp_discovery_info (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
}
}
+xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s,
+ const char *jid,
+ xmlNodePtr *prev_item)
+{
+ xmlNodePtr prev = NULL, cur = s->roster_items;
+ while (cur != NULL) {
+ char *cur_jid = xmlGetProp(cur, "jid");
+ if (cur_jid == NULL) {
+ rexmpp_log(s, LOG_ALERT, "No jid found in a roster item.");
+ return NULL;
+ }
+ int match = (strcmp(cur_jid, jid) == 0);
+ free(cur_jid);
+ if (match) {
+ if (prev_item != NULL) {
+ *prev_item = prev;
+ }
+ return cur;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ return NULL;
+}
+
+rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, xmlNodePtr item) {
+ rexmpp_err_t ret = REXMPP_SUCCESS;
+ if (! rexmpp_xml_match(item, "jabber:iq:roster", "item")) {
+ rexmpp_log(s, LOG_ERR, "No roster item.");
+ return REXMPP_E_PARAM;
+ }
+ char *subscription = xmlGetProp(item, "subscription");
+ char *jid = xmlGetProp(item, "jid");
+ if (subscription != NULL && strcmp(subscription, "remove") == 0) {
+ /* Delete the item. */
+ xmlNodePtr prev, cur;
+ cur = rexmpp_roster_find_item(s, jid, &prev);
+ if (cur != NULL) {
+ if (prev != NULL) {
+ prev->next = cur->next;
+ } else {
+ s->roster_items = cur->next;
+ }
+ xmlFreeNode(cur);
+ } else {
+ ret = REXMPP_E_ROSTER_ITEM_NOT_FOUND;
+ }
+ } else {
+ /* Add or modify the item. */
+ xmlNodePtr cur, prev;
+ cur = rexmpp_roster_find_item(s, jid, &prev);
+ /* Remove the item if it was in the roster before. */
+ if (cur != NULL) {
+ if (prev != NULL) {
+ prev->next = cur->next;
+ } else {
+ s->roster_items = cur->next;
+ }
+ xmlFreeNode(cur);
+ }
+ /* Add the new item. */
+ xmlNodePtr new_item = xmlCopyNode(item, 1);
+ new_item->next = s->roster_items;
+ s->roster_items = new_item;
+ }
+ free(jid);
+ if (subscription != NULL) {
+ free(subscription);
+ }
+ return ret;
+}
+
+void rexmpp_iq_roster_get (rexmpp_t *s,
+ xmlNodePtr req,
+ xmlNodePtr response,
+ int success)
+{
+ if (! success) {
+ rexmpp_log(s, LOG_ERR, "Roster loading failed.");
+ return;
+ }
+ xmlNodePtr query = xmlFirstElementChild(response);
+ if (! rexmpp_xml_match(query, "jabber:iq:roster", "query")) {
+ rexmpp_log(s, LOG_WARNING, "No roster query found.");
+ return;
+ }
+ if (s->roster_items != NULL) {
+ xmlFreeNodeList(s->roster_items);
+ }
+ if (s->roster_ver != NULL) {
+ free(s->roster_ver);
+ }
+ s->roster_ver = xmlGetProp(query, "ver");
+ s->roster_items = xmlFirstElementChild(query);
+}
+
void rexmpp_stream_is_ready(rexmpp_t *s) {
s->stream_state = REXMPP_STREAM_READY;
rexmpp_resend_stanzas(s);
@@ -1042,13 +1185,29 @@ void rexmpp_stream_is_ready(rexmpp_t *s) {
xmlNodePtr disco_query = xmlNewNode(NULL, "query");
xmlNewNs(disco_query, "http://jabber.org/protocol/disco#info", NULL);
rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
- disco_query, rexmpp_discovery_info);
+ disco_query, rexmpp_iq_discovery_info);
+ }
+ if (s->manage_roster) {
+ xmlNodePtr roster_query = xmlNewNode(NULL, "query");
+ xmlNewNs(roster_query, "jabber:iq:roster", NULL);
+ if (s->roster_ver != NULL) {
+ xmlNewProp(roster_query, "ver", s->roster_ver);
+ } else {
+ xmlNewProp(roster_query, "ver", "");
+ }
+ rexmpp_iq_new(s, "get", NULL,
+ roster_query, rexmpp_iq_roster_get);
}
}
/* Resource binding,
https://tools.ietf.org/html/rfc6120#section-7 */
-void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
+void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success) {
+ if (! success) {
+ /* todo: reconnect here? */
+ rexmpp_log(s, LOG_ERR, "Resource binding failed.");
+ return;
+ }
/* todo: handle errors */
xmlNodePtr child = xmlFirstElementChild(response);
if (rexmpp_xml_match(child, "urn:ietf:params:xml:ns:xmpp-bind", "bind")) {
@@ -1089,9 +1248,10 @@ void rexmpp_stream_bind (rexmpp_t *s) {
void rexmpp_process_element(rexmpp_t *s) {
xmlNodePtr elem = s->current_element;
- /* IQ responses */
+ /* IQs */
if (rexmpp_xml_match(elem, "jabber:client", "iq")) {
char *type = xmlGetProp(elem, "type");
+ /* IQ responses. */
if (strcmp(type, "result") == 0 || strcmp(type, "error") == 0) {
char *id = xmlGetProp(elem, "id");
rexmpp_iq_t *req = s->active_iq;
@@ -1101,7 +1261,13 @@ void rexmpp_process_element(rexmpp_t *s) {
if (strcmp(id, req_id) == 0) {
found = 1;
if (req->cb != NULL) {
- req->cb(s, req->request, elem);
+ char *iq_type = xmlGetProp(elem, "type");
+ int success = 0;
+ if (strcmp(type, "result") == 0) {
+ success = 1;
+ }
+ free(iq_type);
+ req->cb(s, req->request, elem, success);
}
/* Remove the callback from the list, but keep in mind that
it could have added more entries. */
@@ -1127,6 +1293,17 @@ void rexmpp_process_element(rexmpp_t *s) {
}
free(id);
}
+ /* IQ "set" requests. */
+ if (strcmp(type, "set") == 0) {
+ xmlNodePtr query = xmlFirstElementChild(elem);
+ if (s->manage_roster &&
+ rexmpp_xml_match(query, "jabber:iq:roster", "query")) {
+ /* Roster push. */
+ rexmpp_modify_roster(s, xmlFirstElementChild(query));
+ /* todo: check for errors */
+ rexmpp_iq_reply(s, elem, "result", NULL);
+ }
+ }
free(type);
}
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 5156142..43c715c 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -29,7 +29,10 @@ typedef struct rexmpp rexmpp_t;
A callback must not free the request or the response, but merely
inspect those and react.
*/
-typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s, xmlNodePtr request, xmlNodePtr response);
+typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s,
+ xmlNodePtr request,
+ xmlNodePtr response,
+ int success);
typedef struct rexmpp_iq rexmpp_iq_t;
@@ -185,7 +188,13 @@ enum rexmpp_err {
/** JID-related error. */
REXMPP_E_JID,
/** Failure to allocate memory. */
- REXMPP_E_MALLOC
+ REXMPP_E_MALLOC,
+ /** Roster-related error. */
+ REXMPP_E_ROSTER,
+ /** A roster item is not found. */
+ REXMPP_E_ROSTER_ITEM_NOT_FOUND,
+ /** An erroneous parameter is supplied. */
+ REXMPP_E_PARAM
};
typedef enum rexmpp_err rexmpp_err_t;
@@ -223,6 +232,7 @@ struct rexmpp
/* Various knobs (these are used instead of loadable modules). */
int enable_carbons;
int enable_service_discovery;
+ int manage_roster;
/* Resource limits. */
uint32_t stanza_queue_size;
@@ -238,6 +248,8 @@ struct rexmpp
/* Stream-related state. */
char *assigned_jid;
xmlNodePtr stream_features;
+ xmlNodePtr roster_items;
+ char *roster_ver;
/* IQs we're waiting for responses to. */
rexmpp_iq_t *active_iq;