diff options
Diffstat (limited to 'src/rexmpp.c')
-rw-r--r-- | src/rexmpp.c | 2896 |
1 files changed, 1815 insertions, 1081 deletions
diff --git a/src/rexmpp.c b/src/rexmpp.c index c4c1e1c..24b9965 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -8,39 +8,100 @@ #include <string.h> #include <sys/time.h> +#include <time.h> #include <errno.h> #include <syslog.h> #include <arpa/nameser.h> - -#include <ares.h> -#include <libxml/tree.h> -#include <libxml/xmlsave.h> -#include <gnutls/gnutls.h> -#include <gnutls/crypto.h> -#include <gnutls/x509.h> -#include <gsasl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <assert.h> +#include <stdbool.h> + +#include "config.h" + +#ifdef HAVE_GCRYPT +#include <gcrypt.h> +#endif +#ifdef USE_UNBOUND +#include <unbound.h> +#endif +#ifdef HAVE_GPGME +#include <gpgme.h> +#endif +#ifdef HAVE_CURL +#include <curl/curl.h> +#endif #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_tcp.h" #include "rexmpp_socks.h" #include "rexmpp_roster.h" +#include "rexmpp_dns.h" +#include "rexmpp_jid.h" +#include "rexmpp_openpgp.h" +#include "rexmpp_console.h" +#include "rexmpp_http_upload.h" +#include "rexmpp_jingle.h" +#include "rexmpp_base64.h" +#include "rexmpp_sasl.h" +#include "rexmpp_random.h" +#include "rexmpp_digest.h" + +struct rexmpp_iq_cacher { + rexmpp_iq_callback_t cb; + void *cb_data; +}; + +struct rexmpp_feature_search { + const char *feature_var; + int max_requests; + int pending; + rexmpp_iq_callback_t cb; + void *cb_data; + int fresh; + int found; +}; + +const char *rexmpp_strerror (rexmpp_err_t error) { + switch (error) { + case REXMPP_SUCCESS: return "No error"; + case REXMPP_E_AGAIN: return "An operation is in progress"; + case REXMPP_E_SEND_QUEUE_FULL: return + "A message can't be queued for sending, because the queue is full"; + case REXMPP_E_STANZA_QUEUE_FULL: return + "The library can't take responsibility for message delivery because " + "XEP-0198 stanza queue is full"; + case REXMPP_E_CANCELLED: return "Cancelled by a user"; + case REXMPP_E_SEND_BUFFER_EMPTY: return + "Attempted to send while send buffer is empty"; + case REXMPP_E_SEND_BUFFER_NOT_EMPTY: return + "Attempted to start sending while send buffer is not empty"; + case REXMPP_E_SASL: return "SASL-related error"; + case REXMPP_E_PGP: return "OpenPGP-related error"; + case REXMPP_E_TLS: return "TLS-related error"; + case REXMPP_E_TCP: return "TCP-related error"; + case REXMPP_E_DNS: return "DNS-related error"; + case REXMPP_E_XML: return "XML-related error"; + case REXMPP_E_JID: return "JID-related error"; + case REXMPP_E_MALLOC: return "Memory allocation failure"; + case REXMPP_E_ROSTER: return "Roster-related error"; + case REXMPP_E_ROSTER_ITEM_NOT_FOUND: return "Roster item is not found"; + case REXMPP_E_PARAM: return "An erroneous parameter is supplied"; + case REXMPP_E_STREAM: return "A stream error"; + case REXMPP_E_OTHER: return "An unspecified error"; + default: return "Unknown error"; + } +} void rexmpp_sax_start_elem_ns (rexmpp_t *s, - const char *localname, - const char *prefix, - const char *URI, - int nb_namespaces, - const char **namespaces, - int nb_attributes, - int nb_defaulted, - const char **attributes); - -void rexmpp_sax_end_elem_ns(rexmpp_t *s, - const char *localname, - const char *prefix, - const char *URI); - -void rexmpp_sax_characters (rexmpp_t *s, const char * ch, int len); + const char *name, + const char *namespace, + rexmpp_xml_attr_t *attributes); + +void rexmpp_sax_end_elem_ns(rexmpp_t *s); + +void rexmpp_sax_characters (rexmpp_t *s, const char * ch, size_t len); void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...) { @@ -52,174 +113,470 @@ void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...) } } -char *rexmpp_capabilities_string (rexmpp_t *s, xmlNodePtr info) { - /* Assuming the info is sorted already. Would be better to sort it - here (todo). */ - xmlNodePtr cur; - int buf_len = 1024, str_len = 0; - char *str = malloc(buf_len); - for (cur = info; cur; cur = cur->next) { - if (strcmp(cur->name, "identity") == 0) { - int cur_len = 5; /* ///< for an empty identity */ - - /* Collect the properties we'll need. */ - char *category = xmlGetProp(cur, "category"); - char *type = xmlGetProp(cur, "type"); - char *lang = xmlGetProp(cur, "xml:lang"); - char *name = xmlGetProp(cur, "name"); - - /* Calculate the length needed. */ - if (category != NULL) { - cur_len += strlen(category); - } - if (type != NULL) { - cur_len += strlen(type); - } - if (lang != NULL) { - cur_len += strlen(lang); - } - if (name != NULL) { - cur_len += strlen(name); - } +rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s, + const char *occupant_jid, + const char *password, + unsigned int delay) +{ + /* At first try to edit an existing record. */ + rexmpp_muc_ping_t *mp = s->muc_ping; + while (mp != NULL) { + if (strcmp(mp->jid, occupant_jid) == 0) { + mp->delay = delay; + return REXMPP_SUCCESS; + } + mp = mp->next; + } - /* Reallocate the buffer if necessary. */ - if (cur_len > buf_len - str_len) { - while (cur_len > buf_len - str_len) { - buf_len *= 2; - } - str = realloc(str, buf_len); - } + /* No existing self-ping record for this occupant JID; create a new + one. */ + mp = malloc(sizeof(rexmpp_muc_ping_t)); + if (mp == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to allocate memory for a MUC self-ping record: %s", + strerror(errno)); + return REXMPP_E_MALLOC; + } + mp->jid = strdup(occupant_jid); + if (mp->jid == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to duplicate JID string for a MUC self-ping record: %s", + strerror(errno)); + free(mp); + return REXMPP_E_OTHER; + } + if (password != NULL) { + mp->password = strdup(password); + } else { + mp->password = NULL; + } - /* Fill the data. */ - if (category != NULL) { - strcpy(str + str_len, category); - str_len += strlen(category); - } - str[str_len] = '/'; - str_len++; - if (type != NULL) { - strcpy(str + str_len, type); - str_len += strlen(type); - } - str[str_len] = '/'; - str_len++; - if (lang != NULL) { - strcpy(str + str_len, lang); - str_len += strlen(lang); - } - str[str_len] = '/'; - str_len++; - if (name != NULL) { - strcpy(str + str_len, name); - str_len += strlen(name); - } - str[str_len] = '<'; - str_len++; + mp->delay = delay; + mp->requested = 0; + mp->last_activity.tv_sec = 0; + mp->last_activity.tv_nsec = 0; + mp->next = s->muc_ping; + s->muc_ping = mp; + return REXMPP_SUCCESS; +} - /* Free the values. */ - if (category != NULL) { - free(category); +rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s, const char *occupant_jid) { + rexmpp_muc_ping_t **mp = &(s->muc_ping); + while (*mp != NULL) { + if (strcmp(occupant_jid, (*mp)->jid) == 0) { + rexmpp_muc_ping_t *found = *mp; + *mp = found->next; + free(found->jid); + if (found->password != NULL) { + free(found->password); } - if (type != NULL) { - free(type); - } - if (lang != NULL) { - free(lang); - } - if (name != NULL) { - free(name); + free(found); + return REXMPP_SUCCESS; + } + mp = &((*mp)->next); + } + rexmpp_log(s, LOG_WARNING, + "Removal of MUC self-ping record for JID %s is requested, " + "but no such record is found", + occupant_jid); + return REXMPP_E_OTHER; +} + +rexmpp_err_t rexmpp_muc_join (rexmpp_t *s, + const char *occupant_jid, + const char *password, + unsigned int ping_delay) +{ + rexmpp_xml_t *presence = + rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full); + rexmpp_xml_add_attr(presence, "to", occupant_jid); + rexmpp_xml_t *x = + rexmpp_xml_new_elem("x", "http://jabber.org/protocol/muc"); + rexmpp_xml_add_child(presence, x); + rexmpp_err_t ret = rexmpp_send(s, presence); + if (ping_delay > 0) { + rexmpp_muc_ping_set(s, occupant_jid, password, ping_delay); + } + return ret; +} + +rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid) { + rexmpp_muc_ping_remove(s, occupant_jid); + rexmpp_xml_t *presence = + rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "from", s->assigned_jid.full); + rexmpp_xml_add_attr(presence, "to", occupant_jid); + rexmpp_xml_add_attr(presence, "type", "unavailable"); + return rexmpp_send(s, presence); +} + +void rexmpp_muc_pong (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + rexmpp_muc_ping_t *mp = ptr; + clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity)); + mp->requested = 0; + if (! success) { + const char *jid = rexmpp_xml_find_attr_val(req, "to"); + rexmpp_xml_t *error = rexmpp_xml_first_elem_child(response); + if (error == NULL) { + rexmpp_log(s, LOG_ERR, + "MUC self-ping failure for %s, and no error element received", + jid); + rexmpp_muc_ping_remove(s, jid); + } else { + rexmpp_log(s, LOG_WARNING, "MUC self-ping failure for %s: %s", + jid, error->alt.elem.qname.name); + if (rexmpp_xml_match(error, NULL, "service-unavailable") || + rexmpp_xml_match(error, NULL, "feature-not-implemented") || + rexmpp_xml_match(error, NULL, "remote-server-not-found") || + rexmpp_xml_match(error, NULL, "remote-server-timeout")) { + rexmpp_log(s, LOG_WARNING, "Giving up on pinging it"); + rexmpp_muc_ping_remove(s, jid); + } else if (rexmpp_xml_match(error, NULL, "item-not-found")) { + /* Ignore and keep pinging? */ + } else { + /* Some other error, re-join. */ + rexmpp_muc_join(s, jid, NULL, 0); } - } else if (strcmp(cur->name, "feature") == 0) { - char *var = xmlGetProp(cur, "var"); - int cur_len = 2 + strlen(var); - if (cur_len > buf_len - str_len) { - while (cur_len > buf_len - str_len) { - buf_len *= 2; + } + } +} + +char *rexmpp_capabilities_hash (rexmpp_t *s, + rexmpp_xml_t *info) +{ + /* Assuming the info is sorted already. Might be useful to sort it + here. */ + rexmpp_digest_t digest_ctx; + if (rexmpp_digest_init(&digest_ctx, REXMPP_DIGEST_SHA1)) { + rexmpp_log(s, LOG_ERR, "Failed to initialize a digest object"); + return NULL; + } + + rexmpp_xml_t *cur; + for (cur = info; cur; cur = cur->next) { + if (strcmp(cur->alt.elem.qname.name, "identity") == 0) { + const char *identity_strings[4] = + { rexmpp_xml_find_attr_val(cur, "category"), + rexmpp_xml_find_attr_val(cur, "type"), + rexmpp_xml_find_attr_val(cur, "xml:lang"), + rexmpp_xml_find_attr_val(cur, "name") + }; + int i; + for (i = 0; i < 4; i++) { + if (identity_strings[i] != NULL) { + rexmpp_digest_update(&digest_ctx, + identity_strings[i], + strlen(identity_strings[i])); } - str = realloc(str, buf_len); + rexmpp_digest_update(&digest_ctx, (i < 3) ? "/" : "<", 1); + } + } else if (strcmp(cur->alt.elem.qname.name, "feature") == 0) { + const char *var = rexmpp_xml_find_attr_val(cur, "var"); + if (var != NULL) { + rexmpp_digest_update(&digest_ctx, var, strlen(var)); + rexmpp_digest_update(&digest_ctx, "<", 1); + } else { + rexmpp_log(s, LOG_ERR, "Found an empty feature var"); } - strcpy(str + str_len, var); - str_len += strlen(var); - str[str_len] = '<'; - str_len++; - free(var); } else { rexmpp_log(s, LOG_ERR, - "Unsupported node type in disco info: %s", cur->name); + "Unsupported node type in disco info: %s", + cur->alt.elem.qname.name); } } - str[str_len] = '\0'; - return str; + + size_t digest_len = rexmpp_digest_len(REXMPP_DIGEST_SHA1); + char *digest_buf = malloc(digest_len); + rexmpp_digest_finish(&digest_ctx, digest_buf, digest_len); + char *digest_base64 = NULL; + size_t digest_base64_len = 0; + rexmpp_base64_to(digest_buf, digest_len, &digest_base64, &digest_base64_len); + free(digest_buf); + return digest_base64; } -char *rexmpp_capabilities_hash (rexmpp_t *s, - xmlNodePtr info) +rexmpp_xml_t *rexmpp_find_event (rexmpp_t *s, + const char *from, + const char *node, + rexmpp_xml_t **prev_event) { - int err; - char *hash; - char *str = rexmpp_capabilities_string(s, info); - err = gsasl_sha1(str, strlen(str), &hash); - free(str); - if (err) { - rexmpp_log(s, LOG_ERR, "Hashing failure: %s", - gsasl_strerror(err)); + rexmpp_xml_t *prev, *cur; + for (prev = NULL, cur = s->roster_events; + cur != NULL; + prev = cur, cur = cur->next) { + const char *cur_from = rexmpp_xml_find_attr_val(cur, "from"); + if (cur_from == NULL) { + continue; + } + rexmpp_xml_t *cur_event = + rexmpp_xml_find_child(cur, + "http://jabber.org/protocol/pubsub#event", + "event"); + rexmpp_xml_t *cur_items = + rexmpp_xml_find_child(cur_event, + "http://jabber.org/protocol/pubsub#event", + "items"); + if (cur_items == NULL) { + continue; + } + const char *cur_node = rexmpp_xml_find_attr_val(cur_items, "node"); + if (cur_node == NULL) { + continue; + } + int match = (strcmp(cur_from, from) == 0 && strcmp(cur_node, node) == 0); + if (match) { + if (prev_event != NULL) { + *prev_event = prev; + } + return cur; + } + } + return NULL; +} + +/* https://docs.modernxmpp.org/client/design/#names */ +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; } - char *out = NULL; - size_t out_len = 0; - gsasl_base64_to(hash, 20, &out, &out_len); - free(hash); - return out; + if (s->manage_roster) { + rexmpp_xml_t *roster_item = rexmpp_roster_find_item(s, jid.bare, NULL); + if (roster_item != NULL) { + const char *name = rexmpp_xml_find_attr_val(roster_item, "name"); + if (name != NULL) { + return strdup(name); + } + } + if (s->track_roster_events) { + rexmpp_xml_t *elem = + rexmpp_find_event(s, jid.bare, "http://jabber.org/protocol/nick", NULL); + if (elem != NULL) { + rexmpp_xml_t *event = + rexmpp_xml_find_child(elem, + "http://jabber.org/protocol/pubsub#event", + "event"); + rexmpp_xml_t *items = + rexmpp_xml_find_child(event, + "http://jabber.org/protocol/pubsub#event", + "items"); + rexmpp_xml_t *item = + rexmpp_xml_find_child(items, + "http://jabber.org/protocol/pubsub#event", + "item"); + if (item != NULL) { + rexmpp_xml_t *nick = + rexmpp_xml_find_child(item, + "http://jabber.org/protocol/nick", + "nick"); + if (nick != NULL && + nick->type == REXMPP_XML_ELEMENT && + nick->alt.elem.children != NULL && + nick->alt.elem.children->type == REXMPP_XML_TEXT) { + return strdup(rexmpp_xml_text_child(nick)); + } + } + } + } + } + if (jid.local[0] != '\0') { + return strdup(jid.local); + } + return strdup(jid.bare); } -xmlNodePtr rexmpp_xml_feature (const char *var) { - xmlNodePtr feature = xmlNewNode(NULL, "feature"); - xmlNewProp(feature, "var", var); +rexmpp_xml_t *rexmpp_xml_feature (const char *var) { + rexmpp_xml_t *feature = rexmpp_xml_new_elem("feature", NULL); + rexmpp_xml_add_attr(feature, "var", var); return feature; } -xmlNodePtr rexmpp_xml_error (const char *type, const char *condition) { - xmlNodePtr error = xmlNewNode(NULL, "error"); - xmlNewProp(error, "type", type); - xmlNodePtr cond = xmlNewNode(NULL, condition); - xmlNewNs(cond, "urn:ietf:params:xml:ns:xmpp-stanzas", NULL); - xmlAddChild(error, cond); - return error; +void rexmpp_disco_find_feature_cb (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *request, + rexmpp_xml_t *response, + int success) +{ + struct rexmpp_feature_search *search = ptr; + if (! success) { + const char *to = rexmpp_xml_find_attr_val(request, "to"); + rexmpp_xml_t *query = request->alt.elem.children; + rexmpp_log(s, LOG_ERR, "Failed to query %s for %s.", to, query->alt.elem.qname.namespace); + } else if (! search->found) { + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(response); + if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", + "query")) { + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query); + while (child != NULL && (! search->found)) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", + "feature")) { + const char *var = rexmpp_xml_find_attr_val(child, "var"); + if (var != NULL) { + if (strcmp(var, search->feature_var) == 0) { + search->cb(s, search->cb_data, request, response, success); + search->found = 1; + } + } + } + child = rexmpp_xml_next_elem_sibling(child); + } + if ((! search->found) && (search->max_requests > 0)) { + /* Still not found, request items */ + const char *jid = rexmpp_xml_find_attr_val(request, "to"); + if (jid != NULL) { + search->pending++; + search->max_requests--; + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", + "http://jabber.org/protocol/disco#items"); + rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, + search, search->fresh); + } + } + } else if (rexmpp_xml_match(query, + "http://jabber.org/protocol/disco#items", + "query")) { + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query); + while (child != NULL && (search->max_requests > 0)) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#items", + "item")) { + const char *jid = rexmpp_xml_find_attr_val(child, "jid"); + if (jid != NULL) { + search->pending++; + search->max_requests--; + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", + "http://jabber.org/protocol/disco#info"); + rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, + search, search->fresh); + } + } + child = rexmpp_xml_next_elem_sibling(child); + } + } + } + search->pending--; + if (search->pending == 0) { + if (! search->found) { + search->cb(s, search->cb_data, NULL, NULL, 0); + } + free(search); + } +} + +rexmpp_err_t +rexmpp_disco_find_feature (rexmpp_t *s, + const char *jid, + const char *feature_var, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh, + int max_requests) +{ + struct rexmpp_feature_search *search = + malloc(sizeof(struct rexmpp_feature_search)); + if (search == NULL) { + return REXMPP_E_MALLOC; + } + search->max_requests = max_requests - 1; + search->found = 0; + search->pending = 1; + search->cb = cb; + search->cb_data = cb_data; + search->fresh = fresh; + search->feature_var = feature_var; + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info"); + if (jid == NULL) { + jid = s->initial_jid.domain; + } + return rexmpp_cached_iq_new(s, "get", jid, query, + rexmpp_disco_find_feature_cb, search, fresh); } -xmlNodePtr rexmpp_xml_default_disco_info () { +rexmpp_xml_t *rexmpp_disco_info (rexmpp_t *s) { + if (s->disco_info != NULL) { + return s->disco_info; + } + rexmpp_xml_t *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; + s->disco_info = rexmpp_xml_new_elem("identity", NULL); + rexmpp_xml_add_attr(s->disco_info, "category", "client"); + rexmpp_xml_add_attr(s->disco_info, "type", s->client_type); + rexmpp_xml_add_attr(s->disco_info, "name", s->client_name); + prev = s->disco_info; + 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; + } + if (s->autojoin_bookmarked_mucs) { + cur = rexmpp_xml_feature("urn:xmpp:bookmarks:1+notify"); + prev->next = cur; + prev = cur; + } + if (s->retrieve_openpgp_keys) { + cur = rexmpp_xml_feature("urn:xmpp:openpgp:0:public-keys+notify"); + prev->next = cur; + prev = cur; + } + if (s->enable_jingle) { + cur = rexmpp_xml_feature("urn:xmpp:jingle:1"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:file-transfer:5"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ibb:1"); + prev->next = cur; + prev = cur; +#ifdef ENABLE_CALLS + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:dtls:0"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ice-udp:1"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:1"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:audio"); + prev->next = cur; + prev = cur; +#endif + } + cur = rexmpp_xml_feature("urn:xmpp:ping"); + prev->next = cur; + prev = cur; + return s->disco_info; } -int rexmpp_sasl_cb (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { - rexmpp_t *s = gsasl_callback_hook_get(ctx); - if (s == NULL || s->sasl_property_cb == NULL) { - return GSASL_NO_CALLBACK; - } - return s->sasl_property_cb(s, prop); -} +struct rexmpp_xml_parser_handlers sax = { + (rexmpp_xml_parser_element_start)rexmpp_sax_start_elem_ns, + (rexmpp_xml_parser_element_end)rexmpp_sax_end_elem_ns, + (rexmpp_xml_parser_characters)rexmpp_sax_characters +}; -rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) +rexmpp_err_t rexmpp_init (rexmpp_t *s, + const char *jid, + log_function_t log_func) { int err; - xmlSAXHandler sax = { - .initialized = XML_SAX2_MAGIC, - .characters = (charactersSAXFunc)rexmpp_sax_characters, - .startElementNs = (startElementNsSAX2Func)rexmpp_sax_start_elem_ns, - .endElementNs = (endElementNsSAX2Func)rexmpp_sax_end_elem_ns, - }; s->tcp_state = REXMPP_TCP_NONE; s->resolver_state = REXMPP_RESOLVER_NONE; @@ -230,113 +587,192 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) s->carbons_state = REXMPP_CARBONS_INACTIVE; s->manual_host = NULL; s->manual_port = 5222; - s->manual_direct_tls = 0; + s->manual_direct_tls = false; s->disco_node = "rexmpp"; s->socks_host = NULL; s->server_host = NULL; - s->enable_carbons = 1; - s->enable_service_discovery = 1; - s->manage_roster = 1; + s->enable_carbons = true; + s->manage_roster = true; s->roster_cache_file = NULL; + s->track_roster_presence = true; + s->track_roster_events = true; + s->nick_notifications = true; +#ifdef HAVE_GPGME + s->retrieve_openpgp_keys = true; +#else + s->retrieve_openpgp_keys = false; +#endif + s->autojoin_bookmarked_mucs = true; + s->tls_policy = REXMPP_TLS_REQUIRE; + s->enable_jingle = true; + s->client_name = PACKAGE_NAME; + s->client_type = "console"; + s->client_version = PACKAGE_VERSION; + s->local_address = NULL; + s->jingle_prefer_rtcp_mux = true; + s->muc_ping_default_delay = 600; s->send_buffer = NULL; s->send_queue = NULL; s->server_srv = NULL; - s->server_srv_cur = NULL; + s->server_srv_cur = -1; s->server_srv_tls = NULL; - s->server_srv_tls_cur = NULL; + s->server_srv_tls_cur = -1; s->server_socket = -1; + s->server_socket_dns_secure = false; s->current_element_root = NULL; s->current_element = NULL; + s->input_queue = NULL; + s->input_queue_last = NULL; s->stream_features = NULL; 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; - s->tls_session_data = NULL; - s->tls_session_data_size = 0; - s->id_counter = 0; + s->iq_cache = NULL; s->reconnect_number = 0; s->next_reconnect_time.tv_sec = 0; - s->next_reconnect_time.tv_usec = 0; - s->initial_jid = NULL; - s->assigned_jid = NULL; + s->next_reconnect_time.tv_nsec = 0; + s->initial_jid.full[0] = '\0'; + s->assigned_jid.full[0] = '\0'; s->stanza_queue_size = 1024; s->send_queue_size = 1024; s->iq_queue_size = 1024; - s->log_function = NULL; + s->iq_cache_size = 1024; + s->max_jingle_sessions = 1024; + s->x509_cert_file = NULL; + s->x509_key_file = NULL; + s->x509_trust_file = NULL; + s->log_function = log_func; s->sasl_property_cb = NULL; s->xml_in_cb = NULL; s->xml_out_cb = NULL; s->roster_modify_cb = NULL; + s->console_print_cb = NULL; + s->socket_cb = NULL; s->ping_delay = 600; - s->ping_requested = 0; - s->last_network_activity = 0; + s->ping_requested = false; + s->last_network_activity.tv_sec = 0; + s->last_network_activity.tv_nsec = 0; + s->muc_ping = NULL; + s->disco_info = NULL; + + s->jingle_rtp_description = + rexmpp_xml_new_elem("description", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(s->jingle_rtp_description, "media", "audio"); + rexmpp_xml_t *pl_type; + +#ifdef HAVE_OPUS + pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(pl_type, "id", "97"); + rexmpp_xml_add_attr(pl_type, "name", "opus"); + rexmpp_xml_add_attr(pl_type, "clockrate", "48000"); + rexmpp_xml_add_attr(pl_type, "channels", "2"); + rexmpp_xml_add_child(s->jingle_rtp_description, pl_type); +#endif + + pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(pl_type, "id", "0"); + rexmpp_xml_add_attr(pl_type, "name", "PCMU"); + rexmpp_xml_add_attr(pl_type, "clockrate", "8000"); + rexmpp_xml_add_attr(pl_type, "channels", "1"); + rexmpp_xml_add_child(s->jingle_rtp_description, pl_type); + + pl_type = rexmpp_xml_new_elem("payload-type", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(pl_type, "id", "8"); + rexmpp_xml_add_attr(pl_type, "name", "PCMA"); + rexmpp_xml_add_attr(pl_type, "clockrate", "8000"); + rexmpp_xml_add_attr(pl_type, "channels", "1"); + rexmpp_xml_add_child(s->jingle_rtp_description, pl_type); if (jid == NULL) { rexmpp_log(s, LOG_CRIT, "No initial JID is provided."); return REXMPP_E_JID; } - s->initial_jid = strdup(jid); + if (rexmpp_jid_parse(jid, &(s->initial_jid))) { + rexmpp_log(s, LOG_CRIT, "Failed to parse the initial JID."); + return REXMPP_E_JID; + } + if (! rexmpp_jid_check(&s->initial_jid)) { + rexmpp_log(s, LOG_CRIT, "An invalid initial JID is provided."); + return REXMPP_E_JID; + } - s->xml_parser = xmlCreatePushParserCtxt(&sax, s, "", 0, NULL); +#ifdef HAVE_GCRYPT + if (! gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { + rexmpp_log(s, LOG_DEBUG, "Initializing libgcrypt"); + if (gcry_check_version(NULL) == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to initialize libgcrypt"); + return REXMPP_E_OTHER; + } + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + } +#endif + + s->xml_parser = rexmpp_xml_parser_new(&sax, s); if (s->xml_parser == NULL) { rexmpp_log(s, LOG_CRIT, "Failed to create an XML parser context."); return REXMPP_E_XML; } - err = ares_library_init(ARES_LIB_INIT_ALL); - if (err != 0) { - rexmpp_log(s, LOG_CRIT, "ares library initialisation error: %s", - ares_strerror(err)); - xmlFreeParserCtxt(s->xml_parser); + if (rexmpp_dns_ctx_init(s)) { + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_DNS; } - err = ares_init(&(s->resolver_channel)); - if (err) { - rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s", - ares_strerror(err)); - ares_library_cleanup(); - xmlFreeParserCtxt(s->xml_parser); - return REXMPP_E_DNS; - } - - err = gnutls_certificate_allocate_credentials(&(s->gnutls_cred)); - if (err) { - rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", - gnutls_strerror(err)); - ares_destroy(s->resolver_channel); - ares_library_cleanup(); - xmlFreeParserCtxt(s->xml_parser); - return REXMPP_E_TLS; - } - err = gnutls_certificate_set_x509_system_trust(s->gnutls_cred); - if (err < 0) { - rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s", - gnutls_strerror(err)); - ares_destroy(s->resolver_channel); - ares_library_cleanup(); - xmlFreeParserCtxt(s->xml_parser); + if (rexmpp_tls_init(s)) { + rexmpp_dns_ctx_deinit(s); + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_TLS; } - err = gsasl_init(&(s->sasl_ctx)); + err = rexmpp_sasl_ctx_init(s); if (err) { - rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s", - gsasl_strerror(err)); - gnutls_certificate_free_credentials(s->gnutls_cred); - ares_destroy(s->resolver_channel); - ares_library_cleanup(); - xmlFreeParserCtxt(s->xml_parser); + rexmpp_tls_deinit(s); + rexmpp_dns_ctx_deinit(s); + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_SASL; } - 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(); + if (rexmpp_jingle_init(s)) { + rexmpp_sasl_ctx_deinit(s); + rexmpp_tls_deinit(s); + rexmpp_dns_ctx_deinit(s); + rexmpp_xml_parser_free(s->xml_parser); + } + +#ifdef HAVE_GPGME + gpgme_check_version(NULL); + err = gpgme_new(&(s->pgp_ctx)); + if (gpg_err_code(err) != GPG_ERR_NO_ERROR) { + rexmpp_log(s, LOG_CRIT, "gpgme initialisation error: %s", + gpgme_strerror(err)); + rexmpp_sasl_ctx_deinit(s); + rexmpp_tls_deinit(s); + rexmpp_dns_ctx_deinit(s); + rexmpp_jingle_stop(s); + rexmpp_xml_parser_free(s->xml_parser); + return REXMPP_E_PGP; + } +#else + s->pgp_ctx = NULL; +#endif +#ifdef HAVE_CURL + if (curl_global_init(CURL_GLOBAL_ALL) != 0) { + rexmpp_log(s, LOG_CRIT, "Failed to initialize curl"); + } + s->curl_multi = curl_multi_init(); + if (s->curl_multi == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to initialize curl_multi"); + /* todo: free other structures and fail */ + } +#else + s->curl_multi = NULL; +#endif return REXMPP_SUCCESS; } @@ -345,19 +781,16 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, const char *jid) structures), but keeps others (e.g., stanza queue and stream ID, since we may resume the stream afterwards). */ void rexmpp_cleanup (rexmpp_t *s) { - if (s->tls_state != REXMPP_TLS_INACTIVE && - s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { - gnutls_deinit(s->gnutls_session); - } + rexmpp_tls_cleanup(s); s->tls_state = REXMPP_TLS_INACTIVE; if (s->sasl_state != REXMPP_SASL_INACTIVE) { - gsasl_finish(s->sasl_session); - s->sasl_session = NULL; + rexmpp_sasl_ctx_cleanup(s); s->sasl_state = REXMPP_SASL_INACTIVE; } if (s->tcp_state == REXMPP_TCP_CONNECTING) { int sock = rexmpp_tcp_conn_finish(&s->server_connection); if (sock != -1) { + rexmpp_log(s, LOG_DEBUG, "TCP disconnected"); close(sock); } s->tcp_state = REXMPP_TCP_NONE; @@ -372,90 +805,136 @@ void rexmpp_cleanup (rexmpp_t *s) { s->send_buffer = NULL; } if (s->stream_features != NULL) { - xmlFreeNode(s->stream_features); + rexmpp_xml_free(s->stream_features); s->stream_features = NULL; } - while (s->send_queue != NULL) { - xmlNodePtr next = xmlNextElementSibling(s->send_queue); - xmlFreeNode(s->send_queue); - s->send_queue = next; + if (s->send_queue != NULL) { + rexmpp_xml_free_list(s->send_queue); + s->send_queue = NULL; } if (s->current_element_root != NULL) { - xmlFreeNode(s->current_element_root); + rexmpp_xml_free_list(s->current_element_root); s->current_element_root = NULL; s->current_element = NULL; } + if (s->input_queue != NULL) { + rexmpp_xml_free_list(s->input_queue); + s->input_queue = NULL; + s->input_queue_last = NULL; + } if (s->server_srv != NULL) { - ares_free_data(s->server_srv); + rexmpp_dns_result_free(s->server_srv); s->server_srv = NULL; - s->server_srv_cur = NULL; + s->server_srv_cur = -1; } if (s->server_srv_tls != NULL) { - ares_free_data(s->server_srv_tls); + rexmpp_dns_result_free(s->server_srv_tls); s->server_srv_tls = NULL; - s->server_srv_tls_cur = NULL; + s->server_srv_tls_cur = -1; } s->sm_state = REXMPP_SM_INACTIVE; s->ping_requested = 0; } +void rexmpp_iq_finish (rexmpp_t *s, + rexmpp_iq_t *iq, + int success, + rexmpp_xml_t *response) +{ + if (iq->cb != NULL) { + iq->cb(s, iq->cb_data, iq->request, response, success); + } + rexmpp_xml_free(iq->request); + free(iq); +} + /* Frees the things that persist through reconnects. */ void rexmpp_done (rexmpp_t *s) { + rexmpp_jingle_stop(s); rexmpp_cleanup(s); - gsasl_done(s->sasl_ctx); - gnutls_certificate_free_credentials(s->gnutls_cred); - ares_destroy(s->resolver_channel); - ares_library_cleanup(); - xmlFreeParserCtxt(s->xml_parser); - if (s->initial_jid != NULL) { - free(s->initial_jid); - s->initial_jid = NULL; +#ifdef HAVE_CURL + curl_multi_cleanup(s->curl_multi); + curl_global_cleanup(); +#endif +#ifdef HAVE_GPGME + gpgme_release(s->pgp_ctx); +#endif + rexmpp_sasl_ctx_deinit(s); + rexmpp_tls_deinit(s); + rexmpp_dns_ctx_deinit(s); + rexmpp_xml_parser_free(s->xml_parser); + if (s->jingle_rtp_description != NULL) { + rexmpp_xml_free(s->jingle_rtp_description); + s->jingle_rtp_description = NULL; } if (s->stream_id != NULL) { free(s->stream_id); s->stream_id = NULL; } if (s->roster_items != NULL) { - xmlFreeNodeList(s->roster_items); + rexmpp_xml_free_list(s->roster_items); s->roster_items = NULL; } + if (s->roster_presence != NULL) { + rexmpp_xml_free_list(s->roster_presence); + s->roster_presence = NULL; + } + if (s->roster_events != NULL) { + rexmpp_xml_free_list(s->roster_events); + s->roster_events = NULL; + } if (s->roster_ver != NULL) { free(s->roster_ver); s->roster_ver = NULL; } + while (s->muc_ping != NULL) { + rexmpp_muc_ping_t *mp_next = s->muc_ping->next; + free(s->muc_ping->jid); + free(s->muc_ping); + s->muc_ping = mp_next; + } if (s->disco_info != NULL) { - xmlFreeNodeList(s->disco_info); + rexmpp_xml_free_list(s->disco_info); s->disco_info = NULL; } - while (s->stanza_queue != NULL) { - xmlNodePtr next = xmlNextElementSibling(s->stanza_queue); - xmlFreeNode(s->send_queue); - s->send_queue = next; + if (s->stanza_queue != NULL) { + rexmpp_xml_free_list(s->stanza_queue); + s->stanza_queue = NULL; } while (s->active_iq != NULL) { rexmpp_iq_t *next = s->active_iq->next; - xmlFreeNode(s->active_iq->request); - free(s->active_iq); + rexmpp_iq_t *iq = s->active_iq; s->active_iq = next; + rexmpp_iq_finish(s, iq, 0, NULL); } - if (s->tls_session_data != NULL) { - free(s->tls_session_data); + if (s->iq_cache != NULL) { + rexmpp_xml_free_list(s->iq_cache); + s->iq_cache = NULL; } } void rexmpp_schedule_reconnect (rexmpp_t *s) { + if (s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED || + s->stream_state == REXMPP_STREAM_CLOSING) { + /* Don't schedule a reconnect if a reconnect-causing condition + happened during closing. */ + return; + } if (s->reconnect_number == 0) { - gnutls_rnd(GNUTLS_RND_NONCE, &s->reconnect_seconds, sizeof(time_t)); + rexmpp_random_buf((char*)&s->reconnect_seconds, sizeof(time_t)); if (s->reconnect_seconds < 0) { s->reconnect_seconds = - s->reconnect_seconds; } s->reconnect_seconds %= 60; } - time_t seconds = s->reconnect_seconds << s->reconnect_number; + time_t seconds = 3600; + if (s->reconnect_number <= 12) { + seconds = s->reconnect_seconds << s->reconnect_number; + } if (seconds > 3600) { seconds = 3600; } - gettimeofday(&(s->next_reconnect_time), NULL); + clock_gettime(CLOCK_MONOTONIC, &(s->next_reconnect_time)); s->next_reconnect_time.tv_sec += seconds; rexmpp_log(s, LOG_DEBUG, "Scheduled reconnect number %d, in %d seconds", s->reconnect_number, @@ -473,100 +952,24 @@ const char *jid_bare_to_host (const char *jid_bare) { return NULL; } -xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node) { - char buf[11]; - snprintf(buf, 11, "%u", s->id_counter); - s->id_counter++; - xmlNewProp(node, "id", buf); - return node; -} - -unsigned int rexmpp_xml_siblings_count (xmlNodePtr node) { - unsigned int i; - for (i = 0; node != NULL; i++) { - node = xmlNextElementSibling(node); - } - return i; -} - -int rexmpp_xml_match (xmlNodePtr node, - const char *namespace, - const char *name) -{ - if (node == NULL) { - return 0; - } - if (name != NULL) { - if (strcmp(name, node->name) != 0) { - return 0; - } - } - if (namespace != NULL) { - if (node->ns == NULL) { - if (strcmp(namespace, "jabber:client") != 0) { - return 0; - } - } else { - if (strcmp(namespace, node->ns->href) != 0) { - return 0; - } - } - } - return 1; -} - -xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node, - const char *namespace, - const char *name) -{ - if (node == NULL) { - return NULL; - } - xmlNodePtr child; - for (child = xmlFirstElementChild(node); - child != NULL; - child = xmlNextElementSibling(child)) - { - if (rexmpp_xml_match(child, namespace, name)) { - return child; - } - } - return NULL; -} - -xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) { +rexmpp_xml_t *rexmpp_xml_set_delay (rexmpp_t *s, rexmpp_xml_t *node) { if (rexmpp_xml_find_child (node, NULL, "delay")) { return node; } char buf[42]; time_t t = time(NULL); - struct tm *local_time = localtime(&t); - strftime(buf, 42, "%FT%T%z", local_time); - xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL); - xmlNewProp(delay, "stamp", buf); - if (s != NULL && s->assigned_jid != NULL) { - xmlNewProp(delay, "from", s->assigned_jid); + struct tm utc_time; + gmtime_r(&t, &utc_time); + strftime(buf, 42, "%FT%TZ", &utc_time); + rexmpp_xml_t *delay = rexmpp_xml_new_elem("delay", NULL); + rexmpp_xml_add_child(node, delay); + rexmpp_xml_add_attr(delay, "stamp", buf); + if (s != NULL && s->assigned_jid.full[0]) { + rexmpp_xml_add_attr(delay, "from", s->assigned_jid.full); } return node; } -char *rexmpp_xml_serialize(xmlNodePtr node) { - xmlBufferPtr buf = xmlBufferCreate(); - xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0); - xmlSaveTree(ctx, node); - xmlSaveFlush(ctx); - unsigned char *out = xmlBufferDetach(buf); - xmlBufferFree(buf); - return out; -} - -int rexmpp_xml_is_stanza (xmlNodePtr node) { - return rexmpp_xml_match(node, "jabber:client", "message") || - rexmpp_xml_match(node, "jabber:client", "iq") || - rexmpp_xml_match(node, "jabber:client", "presence"); -} - - rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len) { int sasl_err; @@ -575,11 +978,9 @@ rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len) return REXMPP_E_SEND_BUFFER_NOT_EMPTY; } if (s->sasl_state == REXMPP_SASL_ACTIVE) { - sasl_err = gsasl_encode (s->sasl_session, data, data_len, - &(s->send_buffer), &(s->send_buffer_len)); - if (sasl_err != GSASL_OK) { - rexmpp_log(s, LOG_ERR, "SASL encoding error: %s", - gsasl_strerror(sasl_err)); + sasl_err = rexmpp_sasl_encode (s, data, data_len, + &(s->send_buffer), &(s->send_buffer_len)); + if (sasl_err) { s->sasl_state = REXMPP_SASL_ERROR; return REXMPP_E_SASL; } @@ -592,7 +993,7 @@ rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len) s->send_buffer_len = data_len; } s->send_buffer_sent = 0; - return REXMPP_E_AGAIN; + return REXMPP_SUCCESS; } rexmpp_err_t rexmpp_send_continue (rexmpp_t *s) @@ -601,109 +1002,118 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s) rexmpp_log(s, LOG_ERR, "nothing to send"); return REXMPP_E_SEND_BUFFER_EMPTY; } - int ret; + ssize_t ret; + rexmpp_tls_err_t err; while (1) { if (s->tls_state == REXMPP_TLS_ACTIVE) { - ret = gnutls_record_send (s->gnutls_session, - s->send_buffer, - s->send_buffer_len); - } else { - ret = send (s->server_socket, - s->send_buffer + s->send_buffer_sent, - s->send_buffer_len - s->send_buffer_sent, - 0); - } - if (ret > 0) { - s->last_network_activity = time(NULL); - s->send_buffer_sent += ret; - if (s->send_buffer_sent == s->send_buffer_len) { - free(s->send_buffer); - s->send_buffer = NULL; - if (s->send_queue != NULL) { - xmlNodePtr node = s->send_queue; - unsigned char *buf = rexmpp_xml_serialize(node); - ret = rexmpp_send_start(s, buf, strlen(buf)); - free(buf); - if (ret != REXMPP_E_AGAIN) { - return ret; - } - s->send_queue = xmlNextElementSibling(s->send_queue); - xmlFreeNode(node); - } else { - return REXMPP_SUCCESS; - } - } - } else { - if (s->tls_state == REXMPP_TLS_ACTIVE) { - if (ret != GNUTLS_E_AGAIN) { + err = rexmpp_tls_send (s, + s->tls, + s->send_buffer, + s->send_buffer_len, + &ret); + if (ret <= 0) { + if (err != REXMPP_TLS_E_AGAIN) { s->tls_state = REXMPP_TLS_ERROR; /* Assume a TCP error for now as well. */ - rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret)); rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_ERROR; rexmpp_schedule_reconnect(s); - return REXMPP_E_TLS; } - } else { + /* Returning E_AGAIN for now, since the error is potentially + recoverable after the scheduled reconnect. */ + return REXMPP_E_AGAIN; + } + } else { + ret = send (s->server_socket, + s->send_buffer + s->send_buffer_sent, + s->send_buffer_len - s->send_buffer_sent, + 0); + if (ret <= 0) { if (errno != EAGAIN) { rexmpp_log(s, LOG_ERR, "TCP send error: %s", strerror(errno)); rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_ERROR; rexmpp_schedule_reconnect(s); - return REXMPP_E_TCP; } + /* E_AGAIN, similarly to the TLS case. */ + return REXMPP_E_AGAIN; + } + } + + assert(ret > 0); + + clock_gettime(CLOCK_MONOTONIC, &(s->last_network_activity)); + s->send_buffer_sent += ret; + if (s->send_buffer_sent == s->send_buffer_len) { + free(s->send_buffer); + s->send_buffer = NULL; + if (s->send_queue != NULL) { + rexmpp_xml_t *node = s->send_queue; + char *buf = rexmpp_xml_serialize(node, 0); + ret = rexmpp_send_start(s, buf, strlen(buf)); + free(buf); + if (ret != REXMPP_SUCCESS) { + return ret; + } + s->send_queue = s->send_queue->next; + rexmpp_xml_free(node); + } else { + return REXMPP_SUCCESS; } - return REXMPP_E_AGAIN; } + } } rexmpp_err_t rexmpp_send_raw (rexmpp_t *s, const void *data, size_t data_len) { int ret = rexmpp_send_start(s, data, data_len); - if (ret != REXMPP_E_AGAIN) { - return ret; + if (ret == REXMPP_SUCCESS) { + ret = rexmpp_send_continue(s); } - return rexmpp_send_continue(s); + return ret; } rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s); -rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) +rexmpp_err_t rexmpp_send (rexmpp_t *s, rexmpp_xml_t *node) { int need_ack = 0; int ret; if (s->xml_out_cb != NULL && s->xml_out_cb(s, node) == 1) { - xmlFreeNode(node); + rexmpp_xml_free(node); rexmpp_log(s, LOG_WARNING, "Message sending was cancelled by xml_out_cb."); return REXMPP_E_CANCELLED; } if (rexmpp_xml_siblings_count(s->send_queue) >= s->send_queue_size) { - xmlFreeNode(node); + rexmpp_xml_free(node); rexmpp_log(s, LOG_ERR, "The send queue is full, not sending."); return REXMPP_E_SEND_QUEUE_FULL; } + rexmpp_console_on_send(s, node); + if (rexmpp_xml_is_stanza(node)) { if (s->sm_state == REXMPP_SM_ACTIVE) { - if (s->stanzas_out_count - s->stanzas_out_acknowledged >= - s->stanza_queue_size) { - xmlFreeNode(node); + if (s->stanzas_out_count >= + s->stanza_queue_size + s->stanzas_out_acknowledged) { + rexmpp_xml_free(node); rexmpp_log(s, LOG_ERR, "The stanza queue is full, not sending."); return REXMPP_E_STANZA_QUEUE_FULL; } need_ack = 1; - xmlNodePtr queued_stanza = rexmpp_xml_set_delay(s, xmlCopyNode(node, 1)); + rexmpp_xml_t *queued_stanza = + rexmpp_xml_set_delay(s, rexmpp_xml_clone(node)); if (s->stanza_queue == NULL) { s->stanza_queue = queued_stanza; } else { - xmlNodePtr last = s->stanza_queue; - while (xmlNextElementSibling(last) != NULL) { - last = xmlNextElementSibling(last); + rexmpp_xml_t *last = s->stanza_queue; + while (last->next != NULL) { + last = last->next; } - xmlAddNextSibling(last, queued_stanza); + last->next = queued_stanza; } } if (s->sm_state != REXMPP_SM_INACTIVE) { @@ -712,10 +1122,10 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) } if (s->send_buffer == NULL) { - unsigned char *buf = rexmpp_xml_serialize(node); + char *buf = rexmpp_xml_serialize(node, 0); ret = rexmpp_send_raw(s, buf, strlen(buf)); free(buf); - xmlFreeNode(node); + rexmpp_xml_free(node); if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) { return ret; } @@ -723,11 +1133,11 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) if (s->send_queue == NULL) { s->send_queue = node; } else { - xmlNodePtr last = s->send_queue; - while (xmlNextElementSibling(last) != NULL) { - last = xmlNextElementSibling(last); + rexmpp_xml_t *last = s->send_queue; + while (last->next != NULL) { + last = last->next; } - xmlAddNextSibling(last, node); + last->next = node; } ret = REXMPP_E_AGAIN; } @@ -738,37 +1148,35 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) } void rexmpp_iq_reply (rexmpp_t *s, - xmlNodePtr req, + rexmpp_xml_t *req, const char *type, - xmlNodePtr payload) + rexmpp_xml_t *payload) { - xmlNodePtr iq_stanza = xmlNewNode(NULL, "iq"); - xmlNewNs(iq_stanza, "jabber:client", NULL); - xmlNewProp(iq_stanza, "type", type); - char *id = xmlGetProp(req, "id"); + rexmpp_xml_t *iq_stanza = rexmpp_xml_new_elem("iq", "jabber:client"); + rexmpp_xml_add_attr(iq_stanza, "type", type); + const char *id = rexmpp_xml_find_attr_val(req, "id"); if (id != NULL) { - xmlNewProp(iq_stanza, "id", id); - free(id); + rexmpp_xml_add_attr(iq_stanza, "id", id); } - char *to = xmlGetProp(req, "from"); + const char *to = rexmpp_xml_find_attr_val(req, "from"); if (to != NULL) { - xmlNewProp(iq_stanza, "to", to); - free(to); + rexmpp_xml_add_attr(iq_stanza, "to", to); } - if (s->assigned_jid != NULL) { - xmlNewProp(iq_stanza, "from", s->assigned_jid); + if (s->assigned_jid.full[0]) { + rexmpp_xml_add_attr(iq_stanza, "from", s->assigned_jid.full); } if (payload != NULL) { - xmlAddChild(iq_stanza, payload); + rexmpp_xml_add_child(iq_stanza, payload); } rexmpp_send(s, iq_stanza); } -void rexmpp_iq_new (rexmpp_t *s, - const char *type, - const char *to, - xmlNodePtr payload, - rexmpp_iq_callback_t cb) +rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, + const char *type, + const char *to, + rexmpp_xml_t *payload, + rexmpp_iq_callback_t cb, + void *cb_data) { unsigned int i; rexmpp_iq_t *prev = NULL, *last = s->active_iq; @@ -777,107 +1185,211 @@ void rexmpp_iq_new (rexmpp_t *s, last = last->next; } if (i >= s->iq_queue_size && s->iq_queue_size > 0) { + assert(prev != NULL); + assert(last != NULL); rexmpp_log(s, LOG_WARNING, "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, 0); - } - xmlFreeNode(last->request); - free(last); + rexmpp_iq_finish(s, last, 0, NULL); } - xmlNodePtr iq_stanza = rexmpp_xml_add_id(s, xmlNewNode(NULL, "iq")); - xmlNewNs(iq_stanza, "jabber:client", NULL); - xmlNewProp(iq_stanza, "type", type); + rexmpp_xml_t *iq_stanza = + rexmpp_xml_new_elem("iq", "jabber:client"); + rexmpp_xml_add_id(iq_stanza); + rexmpp_xml_add_attr(iq_stanza, "type", type); if (to != NULL) { - xmlNewProp(iq_stanza, "to", to); + rexmpp_xml_add_attr(iq_stanza, "to", to); } - if (s->assigned_jid != NULL) { - xmlNewProp(iq_stanza, "from", s->assigned_jid); + if (s->assigned_jid.full[0]) { + rexmpp_xml_add_attr(iq_stanza, "from", s->assigned_jid.full); } - xmlAddChild(iq_stanza, payload); + rexmpp_xml_add_child(iq_stanza, payload); rexmpp_iq_t *iq = malloc(sizeof(rexmpp_iq_t)); - iq->request = xmlCopyNode(iq_stanza, 1); + if (iq == NULL) { + return REXMPP_E_MALLOC; + } + iq->request = rexmpp_xml_clone(iq_stanza); iq->cb = cb; + iq->cb_data = cb_data; iq->next = s->active_iq; s->active_iq = iq; - rexmpp_send(s, iq_stanza); + return rexmpp_send(s, iq_stanza); } +void rexmpp_iq_cache_cb (rexmpp_t *s, + void *cb_data, + rexmpp_xml_t *request, + rexmpp_xml_t *response, + int success) +{ + if (success && response != NULL) { + rexmpp_xml_t *prev_last = NULL, *last = NULL, *ciq = s->iq_cache; + uint32_t size = 0; + while (ciq != NULL && ciq->next != NULL) { + prev_last = last; + last = ciq; + size++; + ciq = ciq->next->next; + } + if (size >= s->iq_queue_size && prev_last != NULL) { + rexmpp_xml_free(last->next); + rexmpp_xml_free(last); + prev_last->next->next = NULL; + } + rexmpp_xml_t *req = rexmpp_xml_clone(request); + rexmpp_xml_t *resp = rexmpp_xml_clone(response); + req->next = resp; + resp->next = s->iq_cache; + s->iq_cache = req; + } + struct rexmpp_iq_cacher *cacher = cb_data; + if (cacher->cb != NULL) { + cacher->cb(s, cacher->cb_data, request, response, success); + } + free(cacher); +} + +rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, + const char *type, + const char *to, + rexmpp_xml_t *payload, + rexmpp_iq_callback_t cb, + void *cb_data, + int fresh) +{ + if (! fresh) { + rexmpp_xml_t *ciq = s->iq_cache; + while (ciq != NULL && ciq->next != NULL) { + rexmpp_xml_t *ciq_pl = ciq->alt.elem.children; + const char *ciq_type = rexmpp_xml_find_attr_val(ciq, "type"); + const char *ciq_to = rexmpp_xml_find_attr_val(ciq, "to"); + int matches = (rexmpp_xml_eq(ciq_pl, payload) && + strcmp(ciq_type, type) == 0 && + strcmp(ciq_to, to) == 0); + if (matches) { + rexmpp_xml_free(payload); + if (cb != NULL) { + cb(s, cb_data, ciq, ciq->next, 1); + } + return REXMPP_SUCCESS; + } + ciq = ciq->next->next; + } + } + struct rexmpp_iq_cacher *cacher = malloc(sizeof(struct rexmpp_iq_cacher)); + cacher->cb = cb; + cacher->cb_data = cb_data; + return rexmpp_iq_new(s, type, to, payload, rexmpp_iq_cache_cb, cacher); +} + + rexmpp_err_t rexmpp_sm_ack (rexmpp_t *s) { char buf[11]; - xmlNodePtr ack = xmlNewNode(NULL, "a"); - xmlNewNs(ack, "urn:xmpp:sm:3", NULL); + rexmpp_xml_t *ack = rexmpp_xml_new_elem("a", "urn:xmpp:sm:3"); snprintf(buf, 11, "%u", s->stanzas_in_count); - xmlNewProp(ack, "h", buf); + rexmpp_xml_add_attr(ack, "h", buf); return rexmpp_send(s, ack); } rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s) { - xmlNodePtr ack = xmlNewNode(NULL, "r"); - xmlNewNs(ack, "urn:xmpp:sm:3", NULL); - return rexmpp_send(s, ack); + rexmpp_xml_t *req = rexmpp_xml_new_elem("r", "urn:xmpp:sm:3"); + return rexmpp_send(s, req); } -void rexmpp_recv (rexmpp_t *s) { +rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem); + +rexmpp_err_t rexmpp_recv (rexmpp_t *s) { char chunk_raw[4096], *chunk; - ssize_t chunk_raw_len, chunk_len; + size_t chunk_len; + ssize_t chunk_raw_len; int sasl_err; + rexmpp_tls_err_t recv_err; + rexmpp_err_t err = REXMPP_SUCCESS; + int tls_was_active; /* Loop here in order to consume data from TLS buffers, which wouldn't show up on select(). */ do { - if (s->tls_state == REXMPP_TLS_ACTIVE) { - chunk_raw_len = gnutls_record_recv(s->gnutls_session, chunk_raw, 4096); + tls_was_active = (s->tls_state == REXMPP_TLS_ACTIVE); + if (tls_was_active) { + recv_err = rexmpp_tls_recv(s, s->tls, chunk_raw, 4096, &chunk_raw_len); } else { chunk_raw_len = recv(s->server_socket, chunk_raw, 4096, 0); } if (chunk_raw_len > 0) { - s->last_network_activity = time(NULL); + clock_gettime(CLOCK_MONOTONIC, &(s->last_network_activity)); if (s->sasl_state == REXMPP_SASL_ACTIVE) { - sasl_err = gsasl_decode(s->sasl_session, chunk_raw, chunk_raw_len, - &chunk, &chunk_len); - if (sasl_err != GSASL_OK) { - rexmpp_log(s, LOG_ERR, "SASL decoding error: %s", - gsasl_strerror(sasl_err)); + sasl_err = rexmpp_sasl_decode(s, chunk_raw, chunk_raw_len, + &chunk, &chunk_len); + if (sasl_err) { s->sasl_state = REXMPP_SASL_ERROR; - return; + return REXMPP_E_SASL; } } else { chunk = chunk_raw; chunk_len = chunk_raw_len; } - xmlParseChunk(s->xml_parser, chunk, chunk_len, 0); + rexmpp_xml_parser_feed(s->xml_parser, chunk, chunk_len, 0); + if (chunk != chunk_raw && chunk != NULL) { + free(chunk); + } + chunk = NULL; + + rexmpp_xml_t *elem; + for (elem = s->input_queue; + /* Skipping everything after an error. Might be better to + process it anyway, but it could lead to more errors if + the processing isn't done carefully. */ + elem != NULL && (err == REXMPP_SUCCESS || err == REXMPP_E_AGAIN); + elem = elem->next) + { + if (s->xml_in_cb != NULL && s->xml_in_cb(s, elem) != 0) { + rexmpp_log(s, LOG_WARNING, + "Message processing was cancelled by xml_in_cb."); + } else { + err = rexmpp_process_element(s, elem); + } + } + rexmpp_xml_free_list(s->input_queue); + s->input_queue = NULL; + s->input_queue_last = NULL; + if (err != REXMPP_SUCCESS && err != REXMPP_E_AGAIN) { + return err; + } } else if (chunk_raw_len == 0) { - if (s->tls_state == REXMPP_TLS_ACTIVE) { + if (tls_was_active) { s->tls_state = REXMPP_TLS_CLOSED; - rexmpp_log(s, LOG_INFO, "TLS disconnected"); + rexmpp_log(s, LOG_DEBUG, "TLS disconnected"); } - rexmpp_log(s, LOG_INFO, "TCP disconnected"); + rexmpp_log(s, LOG_DEBUG, "TCP disconnected"); rexmpp_cleanup(s); - s->tcp_state = REXMPP_TCP_CLOSED; - if (s->stream_state == REXMPP_STREAM_READY) { + if (s->stream_state == REXMPP_STREAM_READY || + s->stream_state == REXMPP_STREAM_ERROR_RECONNECT) { + s->tcp_state = REXMPP_TCP_NONE; rexmpp_schedule_reconnect(s); + return REXMPP_E_AGAIN; + } else { + s->tcp_state = REXMPP_TCP_CLOSED; } } else { - if (s->tls_state == REXMPP_TLS_ACTIVE) { - if (chunk_raw_len != GNUTLS_E_AGAIN) { + if (tls_was_active) { + if (recv_err != REXMPP_TLS_E_AGAIN) { s->tls_state = REXMPP_TLS_ERROR; /* Assume a TCP error for now as well. */ - rexmpp_log(s, LOG_ERR, "TLS recv error: %s", - gnutls_strerror(chunk_raw_len)); rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_ERROR; rexmpp_schedule_reconnect(s); + return REXMPP_E_AGAIN; } } else if (errno != EAGAIN) { rexmpp_log(s, LOG_ERR, "TCP recv error: %s", strerror(errno)); rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_ERROR; rexmpp_schedule_reconnect(s); + return REXMPP_E_AGAIN; } } } while (chunk_raw_len > 0 && s->tcp_state == REXMPP_TCP_CONNECTED); + return err; } rexmpp_err_t rexmpp_stream_open (rexmpp_t *s) { @@ -887,203 +1399,153 @@ rexmpp_err_t rexmpp_stream_open (rexmpp_t *s) { "<stream:stream to='%s' version='1.0' " "xml:lang='en' xmlns='jabber:client' " "xmlns:stream='http://etherx.jabber.org/streams'>", - jid_bare_to_host(s->initial_jid)); + s->initial_jid.domain); s->stream_state = REXMPP_STREAM_OPENING; return rexmpp_send_raw(s, buf, strlen(buf)); } -void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err); +rexmpp_err_t +rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err); -void rexmpp_start_connecting (rexmpp_t *s) { +rexmpp_err_t rexmpp_start_connecting (rexmpp_t *s) { if (s->socks_host == NULL) { rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u", s->server_host, s->server_port); - rexmpp_process_conn_err(s, - rexmpp_tcp_conn_init(&s->server_connection, - s->server_host, - s->server_port)); + return + rexmpp_process_conn_err(s, + rexmpp_tcp_conn_init(s, + &s->server_connection, + s->server_host, + s->server_port)); } else { rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%u via %s:%u", s->server_host, s->server_port, s->socks_host, s->socks_port); - rexmpp_process_conn_err(s, - rexmpp_tcp_conn_init(&s->server_connection, - s->socks_host, - s->socks_port)); + return rexmpp_process_conn_err(s, + rexmpp_tcp_conn_init(s, + &s->server_connection, + s->socks_host, + s->socks_port)); } } -void rexmpp_try_next_host (rexmpp_t *s) { +rexmpp_err_t rexmpp_try_next_host (rexmpp_t *s) { + rexmpp_dns_result_t *cur_result; + int cur_number; /* todo: check priorities and weights */ s->tls_state = REXMPP_TLS_INACTIVE; - if (s->server_srv_tls != NULL && s->server_srv_tls_cur == NULL) { + if (s->server_srv_tls != NULL && s->server_srv_tls_cur == -1) { /* We have xmpps-client records available, but haven't tried any of them yet. */ - s->server_srv_tls_cur = s->server_srv_tls; - s->server_host = s->server_srv_tls_cur->host; - s->server_port = s->server_srv_tls_cur->port; + s->server_srv_tls_cur = 0; + cur_result = s->server_srv_tls; + cur_number = s->server_srv_tls_cur; s->tls_state = REXMPP_TLS_AWAITING_DIRECT; - } else if (s->server_srv_tls_cur != NULL && - s->server_srv_tls_cur->next != NULL) { + } else if (s->server_srv_tls_cur != -1 && + s->server_srv_tls->data[s->server_srv_tls_cur + 1] != NULL) { /* We have tried some xmpps-client records, but there is more. */ - s->server_srv_tls_cur = s->server_srv_tls_cur->next; - s->server_host = s->server_srv_tls_cur->host; - s->server_port = s->server_srv_tls_cur->port; + s->server_srv_tls_cur++; + cur_result = s->server_srv_tls; + cur_number = s->server_srv_tls_cur; s->tls_state = REXMPP_TLS_AWAITING_DIRECT; - } else if (s->server_srv != NULL && s->server_srv_cur == NULL) { + } else if (s->server_srv != NULL && s->server_srv_cur == -1) { /* Starting with xmpp-client records. */ - s->server_srv_cur = s->server_srv; - s->server_host = s->server_srv_cur->host; - s->server_port = s->server_srv_cur->port; - } else if (s->server_srv_tls_cur != NULL && - s->server_srv_tls_cur->next != NULL) { + s->server_srv_cur = 0; + cur_result = s->server_srv; + cur_number = s->server_srv_cur; + } else if (s->server_srv_cur != -1 && + s->server_srv->data[s->server_srv_cur + 1] != NULL) { /* Advancing in xmpp-client records. */ - s->server_srv_cur = s->server_srv_cur->next; - s->server_host = s->server_srv_cur->host; - s->server_port = s->server_srv_cur->port; + s->server_srv_cur++; + cur_result = s->server_srv; + cur_number = s->server_srv_cur; } else { /* No candidate records left to try. Schedule a reconnect. */ + rexmpp_log(s, LOG_DEBUG, + "No candidate hosts left to try, scheduling a reconnect"); rexmpp_cleanup(s); rexmpp_schedule_reconnect(s); - return; + return REXMPP_E_AGAIN; } - rexmpp_start_connecting(s); + + s->server_active_srv = (rexmpp_dns_srv_t *)cur_result->data[cur_number]; + + s->server_host = s->server_active_srv->target; + s->server_port = s->server_active_srv->port; + return rexmpp_start_connecting(s); } -rexmpp_err_t rexmpp_tls_handshake (rexmpp_t *s) { - s->tls_state = REXMPP_TLS_HANDSHAKE; - int ret = gnutls_handshake(s->gnutls_session); - if (ret == GNUTLS_E_AGAIN) { - rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete"); +rexmpp_err_t +rexmpp_process_tls_conn_err (rexmpp_t *s, + rexmpp_tls_err_t err) +{ + if (err == REXMPP_TLS_E_OTHER) { + s->tls_state = REXMPP_TLS_ERROR; + rexmpp_cleanup(s); + rexmpp_schedule_reconnect(s); return REXMPP_E_AGAIN; - } else if (ret == 0) { - int status; - ret = gnutls_certificate_verify_peers3(s->gnutls_session, - jid_bare_to_host(s->initial_jid), - &status); - if (ret || status) { - s->tls_state = REXMPP_TLS_ERROR; - if (ret) { - rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s", - gnutls_strerror(ret)); - } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) { - rexmpp_log(s, LOG_ERR, "Unexpected certificate owner"); - } else { - rexmpp_log(s, LOG_ERR, "Untrusted certificate"); - } - gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR); - rexmpp_cleanup(s); - rexmpp_schedule_reconnect(s); - return REXMPP_E_TLS; - } + } else if (err == REXMPP_TLS_SUCCESS) { + rexmpp_log(s, LOG_DEBUG, "A TLS connection is established"); s->tls_state = REXMPP_TLS_ACTIVE; - rexmpp_log(s, LOG_DEBUG, "TLS ready"); - - if (gnutls_session_is_resumed(s->gnutls_session)) { - rexmpp_log(s, LOG_INFO, "TLS session is resumed"); - } else { - if (s->tls_session_data != NULL) { - rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed"); - free(s->tls_session_data); - s->tls_session_data = NULL; - } - gnutls_session_get_data(s->gnutls_session, NULL, - &s->tls_session_data_size); - s->tls_session_data = malloc(s->tls_session_data_size); - ret = gnutls_session_get_data(s->gnutls_session, s->tls_session_data, - &s->tls_session_data_size); - if (ret != GNUTLS_E_SUCCESS) { - rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s", - gnutls_strerror(ret)); - return REXMPP_E_TLS; - } - } - if (s->stream_state == REXMPP_STREAM_NONE) { /* It's a direct TLS connection, so open a stream after connecting. */ return rexmpp_stream_open(s); } else { /* A STARTTLS connection, restart the stream. */ - s->stream_state = REXMPP_STREAM_RESTART; - return REXMPP_SUCCESS; + s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser); + return rexmpp_stream_open(s); } } else { - rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s", - gnutls_strerror(ret)); - rexmpp_cleanup(s); - rexmpp_schedule_reconnect(s); - return REXMPP_E_TLS; + s->tls_state = REXMPP_TLS_HANDSHAKE; + return REXMPP_E_AGAIN; } } -rexmpp_err_t rexmpp_tls_start (rexmpp_t *s) { - gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")}; - rexmpp_log(s, LOG_DEBUG, "starting TLS"); - gnutls_init(&s->gnutls_session, GNUTLS_CLIENT); - gnutls_session_set_ptr(s->gnutls_session, s); - gnutls_alpn_set_protocols(s->gnutls_session, &xmpp_client_protocol, 1, 0); - gnutls_server_name_set(s->gnutls_session, GNUTLS_NAME_DNS, - jid_bare_to_host(s->initial_jid), - strlen(jid_bare_to_host(s->initial_jid))); - gnutls_set_default_priority(s->gnutls_session); - gnutls_credentials_set(s->gnutls_session, GNUTLS_CRD_CERTIFICATE, - s->gnutls_cred); - gnutls_transport_set_int(s->gnutls_session, s->server_socket); - gnutls_handshake_set_timeout(s->gnutls_session, - GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - if (s->tls_session_data != NULL) { - int ret = gnutls_session_set_data(s->gnutls_session, - s->tls_session_data, - s->tls_session_data_size); - if (ret != GNUTLS_E_SUCCESS) { - rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s", - gnutls_strerror(ret)); - free(s->tls_session_data); - s->tls_session_data = NULL; - s->tls_session_data_size = 0; - } - } - s->tls_state = REXMPP_TLS_HANDSHAKE; - return rexmpp_tls_handshake(s); -} - rexmpp_err_t rexmpp_connected_to_server (rexmpp_t *s) { s->tcp_state = REXMPP_TCP_CONNECTED; - rexmpp_log(s, LOG_INFO, "Connected to the server"); + rexmpp_log(s, LOG_INFO, + "Connected to the server, the used address record was %s", + s->server_socket_dns_secure ? "secure" : "not secure"); s->reconnect_number = 0; - xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8"); + s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser); if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) { - return rexmpp_tls_start(s); + return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s)); } else { return rexmpp_stream_open(s); } } -void rexmpp_process_socks_err (rexmpp_t *s, enum socks_err err) { +rexmpp_err_t rexmpp_process_socks_err (rexmpp_t *s, enum socks_err err) { if (err == REXMPP_SOCKS_CONNECTED) { - rexmpp_connected_to_server(s); + return rexmpp_connected_to_server(s); } else if (err != REXMPP_SOCKS_E_AGAIN) { rexmpp_log(s, LOG_ERR, "SOCKS5 connection failed."); s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE; close(s->server_socket); s->server_socket = -1; - rexmpp_try_next_host(s); + return rexmpp_try_next_host(s); } + return REXMPP_E_AGAIN; } -void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err) { +rexmpp_err_t +rexmpp_process_conn_err (rexmpp_t *s, + enum rexmpp_tcp_conn_error err) +{ s->tcp_state = REXMPP_TCP_CONNECTING; if (err == REXMPP_CONN_DONE) { + s->server_socket_dns_secure = s->server_connection.dns_secure; s->server_socket = rexmpp_tcp_conn_finish(&s->server_connection); if (s->socks_host == NULL) { - rexmpp_connected_to_server(s); + return rexmpp_connected_to_server(s); } else { s->tcp_state = REXMPP_TCP_SOCKS; - rexmpp_process_socks_err(s, rexmpp_socks_init(&s->server_socks_conn, - s->server_socket, - s->server_host, - s->server_port)); + return + rexmpp_process_socks_err(s, rexmpp_socks_init(&s->server_socks_conn, + s->server_socket, + s->server_host, + s->server_port)); } } else if (err != REXMPP_CONN_IN_PROGRESS) { if (err == REXMPP_CONN_ERROR) { @@ -1092,80 +1554,45 @@ void rexmpp_process_conn_err (rexmpp_t *s, enum rexmpp_tcp_conn_error err) { s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE; } rexmpp_tcp_conn_finish(&s->server_connection); - rexmpp_try_next_host(s); + return rexmpp_try_next_host(s); } + return REXMPP_E_AGAIN; } -void rexmpp_after_srv (rexmpp_t *s) { +void rexmpp_srv_cb (rexmpp_t *s, + void *ptr, + rexmpp_dns_result_t *result) +{ + char *type = ptr; + if (result != NULL) { + rexmpp_log(s, + result->secure ? LOG_DEBUG : LOG_WARNING, + "Resolved a %s SRV record (%s)", + type, result->secure ? "secure" : "not secure"); + if (strncmp("xmpp", type, 5) == 0) { + s->server_srv = result; + } else { + s->server_srv_tls = result; + } + } if (s->resolver_state == REXMPP_RESOLVER_SRV) { s->resolver_state = REXMPP_RESOLVER_SRV_2; } else if (s->resolver_state == REXMPP_RESOLVER_SRV_2) { s->resolver_state = REXMPP_RESOLVER_READY; } - if (s->resolver_state != REXMPP_RESOLVER_READY) { - return; - } - - /* todo: sort the records */ - - if (s->server_srv == NULL && s->server_srv_tls == NULL) { - /* Failed to resolve anything: a fallback. */ - s->server_host = jid_bare_to_host(s->initial_jid); - s->server_port = 5222; - rexmpp_start_connecting(s); - } else { - rexmpp_try_next_host(s); - } -} - -void rexmpp_srv_tls_cb (void *s_ptr, - int status, - int timeouts, - unsigned char *abuf, - int alen) -{ - rexmpp_t *s = s_ptr; - if (status == ARES_SUCCESS) { - ares_parse_srv_reply(abuf, alen, &(s->server_srv_tls)); - } else { - rexmpp_log(s, LOG_WARNING, "Failed to query an xmpps-client SRV record: %s", - ares_strerror(status)); - } - if (status != ARES_EDESTRUCTION) { - rexmpp_after_srv(s); - } -} - -void rexmpp_srv_cb (void *s_ptr, - int status, - int timeouts, - unsigned char *abuf, - int alen) -{ - rexmpp_t *s = s_ptr; - if (status == ARES_SUCCESS) { - ares_parse_srv_reply(abuf, alen, &(s->server_srv)); - } else { - rexmpp_log(s, LOG_WARNING, "Failed to query an xmpp-client SRV record: %s", - ares_strerror(status)); - } - if (status != ARES_EDESTRUCTION) { - rexmpp_after_srv(s); - } } - /* Should be called after reconnect, and after rexmpp_sm_handle_ack in case of resumption. */ rexmpp_err_t rexmpp_resend_stanzas (rexmpp_t *s) { uint32_t i, count; rexmpp_err_t ret = REXMPP_SUCCESS; - xmlNodePtr sq; + rexmpp_xml_t *sq; count = s->stanzas_out_count - s->stanzas_out_acknowledged; for (i = 0; i < count && s->stanza_queue != NULL; i++) { - sq = xmlNextElementSibling(s->stanza_queue); + sq = s->stanza_queue->next; ret = rexmpp_send(s, s->stanza_queue); - if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) { + if (ret > REXMPP_E_AGAIN) { return ret; } s->stanza_queue = sq; @@ -1180,12 +1607,11 @@ rexmpp_err_t rexmpp_resend_stanzas (rexmpp_t *s) { return ret; } -void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) { - char *h = xmlGetProp(elem, "h"); +void rexmpp_sm_handle_ack (rexmpp_t *s, rexmpp_xml_t *elem) { + const char *h = rexmpp_xml_find_attr_val(elem, "h"); if (h != NULL) { uint32_t prev_ack = s->stanzas_out_acknowledged; s->stanzas_out_acknowledged = strtoul(h, NULL, 10); - xmlFree(h); rexmpp_log(s, LOG_DEBUG, "server acknowledged %u out of %u sent stanzas", s->stanzas_out_acknowledged, @@ -1194,8 +1620,8 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) { if (prev_ack <= s->stanzas_out_acknowledged) { uint32_t i; for (i = prev_ack; i < s->stanzas_out_acknowledged; i++) { - xmlNodePtr sq = xmlNextElementSibling(s->stanza_queue); - xmlFreeNode(s->stanza_queue); + rexmpp_xml_t *sq = s->stanza_queue->next; + rexmpp_xml_free(s->stanza_queue); s->stanza_queue = sq; } } else { @@ -1213,10 +1639,14 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) { } void rexmpp_carbons_enabled (rexmpp_t *s, - xmlNodePtr req, - xmlNodePtr response, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { + (void)ptr; + (void)req; /* The request is always the same. */ + (void)response; /* Only checking whether it's a success. */ if (success) { rexmpp_log(s, LOG_INFO, "carbons enabled"); s->carbons_state = REXMPP_CARBONS_ACTIVE; @@ -1227,44 +1657,34 @@ void rexmpp_carbons_enabled (rexmpp_t *s, } void rexmpp_pong (rexmpp_t *s, - xmlNodePtr req, - xmlNodePtr response, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { + (void)ptr; + (void)req; + (void)response; + (void)success; s->ping_requested = 0; } -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")) { - xmlNodePtr child; - for (child = xmlFirstElementChild(query); - child != NULL; - child = xmlNextElementSibling(child)) - { - if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", - "feature")) { - char *var = xmlGetProp(child, "var"); - if (s->enable_carbons && - strcmp(var, "urn:xmpp:carbons:2") == 0) { - xmlNodePtr carbons_enable = xmlNewNode(NULL, "enable"); - xmlNewNs(carbons_enable, "urn:xmpp:carbons:2", NULL); - s->carbons_state = REXMPP_CARBONS_NEGOTIATION; - rexmpp_iq_new(s, "set", NULL, carbons_enable, - rexmpp_carbons_enabled); - } - free(var); - } - } +void rexmpp_disco_carbons_cb (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) { + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_xml_t *carbons_enable = + rexmpp_xml_new_elem("enable", "urn:xmpp:carbons:2"); + s->carbons_state = REXMPP_CARBONS_NEGOTIATION; + rexmpp_iq_new(s, "set", NULL, carbons_enable, + rexmpp_carbons_enabled, NULL); + } else { + rexmpp_log(s, LOG_WARNING, "Failed to discover the carbons feature."); } } @@ -1272,66 +1692,76 @@ void rexmpp_stream_is_ready(rexmpp_t *s) { s->stream_state = REXMPP_STREAM_READY; rexmpp_resend_stanzas(s); - if (s->enable_service_discovery) { - 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_iq_discovery_info); + if (s->enable_carbons) { + rexmpp_disco_find_feature (s, s->initial_jid.domain, + "urn:xmpp:carbons:2", + rexmpp_disco_carbons_cb, + NULL, 0, 1); } if (s->manage_roster) { if (s->roster_cache_file != NULL) { rexmpp_roster_cache_read(s); } - xmlNodePtr roster_query = xmlNewNode(NULL, "query"); - xmlNewNs(roster_query, "jabber:iq:roster", NULL); + rexmpp_xml_t *roster_query = + rexmpp_xml_new_elem("query", "jabber:iq:roster"); if (s->roster_ver != NULL) { - xmlNewProp(roster_query, "ver", s->roster_ver); + rexmpp_xml_add_attr(roster_query, "ver", s->roster_ver); } else { - xmlNewProp(roster_query, "ver", ""); + rexmpp_xml_add_attr(roster_query, "ver", ""); } rexmpp_iq_new(s, "get", NULL, - roster_query, rexmpp_iq_roster_get); + roster_query, rexmpp_iq_roster_get, NULL); } - xmlNodePtr presence = xmlNewNode(NULL, "presence"); - char *caps_hash = rexmpp_capabilities_hash(s, s->disco_info); + rexmpp_xml_t *presence = + rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + + char *caps_hash = rexmpp_capabilities_hash(s, rexmpp_disco_info(s)); if (caps_hash != NULL) { - xmlNodePtr c = xmlNewNode(NULL, "c"); - xmlNewNs(c, "http://jabber.org/protocol/caps", NULL); - xmlNewProp(c, "hash", "sha-1"); - xmlNewProp(c, "node", s->disco_node); - xmlNewProp(c, "ver", caps_hash); - xmlAddChild(presence, c); + rexmpp_xml_t *c = + rexmpp_xml_new_elem("c", "http://jabber.org/protocol/caps"); + rexmpp_xml_add_attr(c, "hash", "sha-1"); + rexmpp_xml_add_attr(c, "node", s->disco_node); + rexmpp_xml_add_attr(c, "ver", caps_hash); + rexmpp_xml_add_child(presence, c); free(caps_hash); } + rexmpp_send(s, presence); } /* Resource binding, https://tools.ietf.org/html/rfc6120#section-7 */ -void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success) { +void rexmpp_bound (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; if (! success) { /* todo: reconnect here? */ rexmpp_log(s, LOG_ERR, "Resource binding failed."); return; } /* todo: handle errors */ - xmlNodePtr child = xmlFirstElementChild(response); + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(response); if (rexmpp_xml_match(child, "urn:ietf:params:xml:ns:xmpp-bind", "bind")) { - xmlNodePtr jid = xmlFirstElementChild(child); + rexmpp_xml_t *jid = rexmpp_xml_first_elem_child(child); if (rexmpp_xml_match(jid, "urn:ietf:params:xml:ns:xmpp-bind", "jid")) { - rexmpp_log(s, LOG_INFO, "jid: %s", xmlNodeGetContent(jid)); - s->assigned_jid = malloc(strlen(xmlNodeGetContent(jid)) + 1); - strcpy(s->assigned_jid, xmlNodeGetContent(jid)); + const char *jid_str = rexmpp_xml_text_child(jid); + rexmpp_log(s, LOG_INFO, "jid: %s", jid_str); + rexmpp_jid_parse(jid_str, &(s->assigned_jid)); } if (s->stream_id == NULL && - (child = rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3", - "sm"))) { + (rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3", + "sm") != NULL)) { /* Try to resume a stream. */ s->sm_state = REXMPP_SM_NEGOTIATION; s->stream_state = REXMPP_STREAM_SM_FULL; - xmlNodePtr sm_enable = xmlNewNode(NULL, "enable"); - xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL); - xmlNewProp(sm_enable, "resume", "true"); + rexmpp_xml_t *sm_enable = + rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3"); rexmpp_send(s, sm_enable); s->stanzas_out_count = 0; s->stanzas_out_acknowledged = 0; @@ -1343,265 +1773,413 @@ void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response, int success } } -void rexmpp_stream_bind (rexmpp_t *s) { +rexmpp_err_t rexmpp_stream_bind (rexmpp_t *s) { /* Issue a bind request. */ s->stream_state = REXMPP_STREAM_BIND; - xmlNodePtr bind_cmd = xmlNewNode(NULL, "bind"); - xmlNewNs(bind_cmd, "urn:ietf:params:xml:ns:xmpp-bind", NULL); - rexmpp_iq_new(s, "set", NULL, bind_cmd, rexmpp_bound); + rexmpp_xml_t *bind_cmd = + rexmpp_xml_new_elem("bind", "urn:ietf:params:xml:ns:xmpp-bind"); + return rexmpp_iq_new(s, "set", NULL, bind_cmd, rexmpp_bound, NULL); } -void rexmpp_process_element (rexmpp_t *s) { - xmlNodePtr elem = s->current_element; +rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem) { + rexmpp_console_on_recv(s, elem); + + /* Stream negotiation, + https://tools.ietf.org/html/rfc6120#section-4.3 */ + if (s->stream_state == REXMPP_STREAM_NEGOTIATION) { + if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) { + + /* Remember features. */ + if (s->stream_features != NULL) { + rexmpp_xml_free(s->stream_features); + } + s->stream_features = rexmpp_xml_clone(elem); + + /* TODO: check for required features properly here. Currently + assuming that STARTTLS, SASL, and BIND (with an exception for + SM) are always required if they are present. */ + rexmpp_xml_t *starttls = + rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls", + "starttls"); + rexmpp_xml_t *sasl = + rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl", + "mechanisms"); + rexmpp_xml_t *bind = + rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind"); + rexmpp_xml_t *sm = + rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm"); + + if (starttls != NULL) { + /* Go for TLS, unless we're both trying to avoid it, and have + other options. */ + if (! (s->tls_policy == REXMPP_TLS_AVOID && + (sasl != NULL || bind != NULL || sm != NULL))) { + s->stream_state = REXMPP_STREAM_STARTTLS; + rexmpp_xml_t *starttls_cmd = + rexmpp_xml_new_elem("starttls", "urn:ietf:params:xml:ns:xmpp-tls"); + rexmpp_send(s, starttls_cmd); + return REXMPP_SUCCESS; + } + } else if (s->tls_policy == REXMPP_TLS_REQUIRE && + s->tls_state != REXMPP_TLS_ACTIVE) { + /* TLS is required, not established, and there's no such + feature available; fail here. */ + rexmpp_log(s, LOG_ERR, + "TLS is required, but the server doesn't advertise such a feature"); + return REXMPP_E_TLS; + } + + /* Nothing to negotiate. */ + if (rexmpp_xml_first_elem_child(elem) == NULL) { + rexmpp_stream_is_ready(s); + return REXMPP_SUCCESS; + } + + if (sasl != NULL) { + s->stream_state = REXMPP_STREAM_SASL; + s->sasl_state = REXMPP_SASL_NEGOTIATION; + char mech_list[2048]; /* todo: perhaps grow it dynamically */ + mech_list[0] = '\0'; + rexmpp_xml_t *mechanism; + for (mechanism = rexmpp_xml_first_elem_child(sasl); + mechanism != NULL; + mechanism = rexmpp_xml_next_elem_sibling(mechanism)) { + if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl", + "mechanism")) { + const char *mech_str = rexmpp_xml_text_child(mechanism); + snprintf(mech_list + strlen(mech_list), + 2048 - strlen(mech_list), + "%s ", + mech_str); + } + } + const char *mech = rexmpp_sasl_suggest_mechanism(s, mech_list); + if (mech == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to decide on a SASL mechanism"); + s->sasl_state = REXMPP_SASL_ERROR; + return REXMPP_E_SASL; + } + rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech); + char *sasl_buf; + if (rexmpp_sasl_start(s, mech)) { + s->sasl_state = REXMPP_SASL_ERROR; + return REXMPP_E_SASL; + } + if (rexmpp_sasl_step64(s, "", (char**)&sasl_buf)) { + s->sasl_state = REXMPP_SASL_ERROR; + return REXMPP_E_SASL; + } + rexmpp_xml_t *auth_cmd = + rexmpp_xml_new_elem("auth", "urn:ietf:params:xml:ns:xmpp-sasl"); + rexmpp_xml_add_attr(auth_cmd, "mechanism", mech); + rexmpp_xml_add_text(auth_cmd, sasl_buf); + free(sasl_buf); + rexmpp_send(s, auth_cmd); + return REXMPP_SUCCESS; + } + + if (s->stream_id != NULL && sm != NULL) { + s->stream_state = REXMPP_STREAM_SM_RESUME; + char buf[11]; + snprintf(buf, 11, "%u", s->stanzas_in_count); + rexmpp_xml_t *sm_resume = + rexmpp_xml_new_elem("resume", "urn:xmpp:sm:3"); + rexmpp_xml_add_attr(sm_resume, "previd", s->stream_id); + rexmpp_xml_add_attr(sm_resume, "h", buf); + rexmpp_send(s, sm_resume); + return REXMPP_SUCCESS; + } + + if (bind != NULL) { + return rexmpp_stream_bind(s); + } + } else { + rexmpp_log(s, LOG_ERR, "Expected stream features, received %s", + elem->alt.elem.qname.name); + return REXMPP_E_STREAM; + } + } /* IQs. These are the ones that should be processed by the library; if a user-facing application wants to handle them on its own, it should cancel further processing by the library (so we can send errors for unhandled IQs here). */ if (rexmpp_xml_match(elem, "jabber:client", "iq")) { - char *type = xmlGetProp(elem, "type"); + const char *type = rexmpp_xml_find_attr_val(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; + const char *id = rexmpp_xml_find_attr_val(elem, "id"); + rexmpp_iq_t *req = s->active_iq, *prev_req = NULL; int found = 0; while (req != NULL && found == 0) { - char *req_id = xmlGetProp(req->request, "id"); - char *req_to = xmlGetProp(req->request, "to"); - char *rep_from = xmlGetProp(elem, "from"); - int id_matches = (strcmp(id, req_id) == 0); + const char *req_id = rexmpp_xml_find_attr_val(req->request, "id"); + const char *req_to = rexmpp_xml_find_attr_val(req->request, "to"); + const char *rep_from = rexmpp_xml_find_attr_val(elem, "from"); + rexmpp_iq_t *req_next = req->next; + int id_matches = (req_id != NULL) && (strcmp(id, req_id) == 0); int jid_matches = 0; - if (req_to == NULL && rep_from == NULL) { + if (rep_from == NULL) { jid_matches = 1; } else if (req_to != NULL && rep_from != NULL) { jid_matches = (strcmp(req_to, rep_from) == 0); } if (id_matches && jid_matches) { found = 1; - if (req->cb != NULL) { - 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); + int success = 0; + if (strcmp(type, "result") == 0) { + success = 1; } - /* Remove the callback from the list, but keep in mind that - it could have added more entries. */ - if (s->active_iq == req) { - s->active_iq = req->next; + /* Remove the callback from the list. */ + if (prev_req == NULL) { + s->active_iq = req_next; } else { - rexmpp_iq_t *prev_req = s->active_iq; - for (prev_req = s->active_iq; - prev_req != NULL; - prev_req = prev_req->next) - { - if (prev_req->next == req) { - prev_req->next = req->next; - break; - } - } + prev_req->next = req_next; } - xmlFreeNode(req->request); - free(req); + /* Finish and free the IQ request structure. */ + rexmpp_iq_finish(s, req, success, elem); } - if (req_to != NULL) { - free(req_to); - } - if (rep_from != NULL) { - free(rep_from); - } - free(req_id); - req = req->next; + prev_req = req; + req = req_next; } - free(id); - } - /* IQ "set" requests. */ - if (strcmp(type, "set") == 0) { - xmlNodePtr query = xmlFirstElementChild(elem); - int from_server = 0; - char *from = xmlGetProp(elem, "from"); - if (from == NULL) { - from_server = 1; - } else { - if (strcmp(from, jid_bare_to_host(s->assigned_jid)) == 0) { + } else if (! rexmpp_jingle_iq(s, elem)) { + if (strcmp(type, "set") == 0) { + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem); + int from_server = 0; + const char *from = rexmpp_xml_find_attr_val(elem, "from"); + if (from == NULL) { from_server = 1; + } else { + if (strcmp(from, s->initial_jid.domain) == 0) { + from_server = 1; + } } - free(from); - } - if (from_server && - s->manage_roster && - rexmpp_xml_match(query, "jabber:iq:roster", "query")) { - /* Roster push. */ - if (s->roster_ver != NULL) { - free(s->roster_ver); - } - s->roster_ver = xmlGetProp(query, "ver"); - rexmpp_modify_roster(s, xmlFirstElementChild(query)); - /* todo: check for errors */ - rexmpp_iq_reply(s, elem, "result", NULL); - if (s->roster_cache_file != NULL) { - rexmpp_roster_cache_write(s); - } - } else { - /* An unknown request. */ - rexmpp_iq_reply(s, elem, "error", - rexmpp_xml_error("cancel", "service-unavailable")); - } - } - /* IQ "get" requests. */ - if (strcmp(type, "get") == 0) { - xmlNodePtr query = xmlFirstElementChild(elem); - if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) { - char *node = xmlGetProp(query, "node"); - char *caps_hash = rexmpp_capabilities_hash(s, s->disco_info); - if (node == NULL || - (caps_hash != NULL && - s->disco_node != NULL && - strlen(node) == strlen(s->disco_node) + 1 + strlen(caps_hash) && - strncmp(node, s->disco_node, strlen(s->disco_node)) == 0 && - node[strlen(s->disco_node)] == '#' && - strcmp(node + strlen(s->disco_node) + 1, caps_hash) == 0)) { - xmlNodePtr result = xmlNewNode(NULL, "query"); - xmlNewNs(result, "http://jabber.org/protocol/disco#info", NULL); - if (node != NULL) { - xmlNewProp(result, "node", node); + if (from_server && + s->manage_roster && + rexmpp_xml_match(query, "jabber:iq:roster", "query")) { + /* Roster push. */ + if (s->roster_ver != NULL) { + free(s->roster_ver); + } + s->roster_ver = NULL; + const char *roster_ver = rexmpp_xml_find_attr_val(query, "ver"); + if (roster_ver != NULL) { + s->roster_ver = strdup(roster_ver); + } + rexmpp_modify_roster(s, rexmpp_xml_first_elem_child(query)); + /* todo: check for errors */ + rexmpp_iq_reply(s, elem, "result", NULL); + if (s->roster_cache_file != NULL) { + rexmpp_roster_cache_write(s); } - xmlAddChild(result, xmlCopyNodeList(s->disco_info)); - rexmpp_iq_reply(s, elem, "result", result); } else { - rexmpp_log(s, LOG_WARNING, - "Service discovery request for an unknown node: %s", node); + /* An unknown request. */ rexmpp_iq_reply(s, elem, "error", - rexmpp_xml_error("cancel", "item-not-found")); + rexmpp_xml_error("cancel", "service-unavailable")); } - if (caps_hash != NULL) { - free(caps_hash); - } - if (node != NULL) { - free(node); + } else if (strcmp(type, "get") == 0) { + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem); + if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) { + const char *node = rexmpp_xml_find_attr_val(query, "node"); + char *caps_hash = rexmpp_capabilities_hash(s, rexmpp_disco_info(s)); + if (node == NULL || + (caps_hash != NULL && + s->disco_node != NULL && + strlen(node) == strlen(s->disco_node) + 1 + strlen(caps_hash) && + strncmp(node, s->disco_node, strlen(s->disco_node)) == 0 && + node[strlen(s->disco_node)] == '#' && + strcmp(node + strlen(s->disco_node) + 1, caps_hash) == 0)) { + rexmpp_xml_t *result = + rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info"); + if (node != NULL) { + rexmpp_xml_add_attr(result, "node", node); + } + rexmpp_xml_add_child(result, + rexmpp_xml_clone_list(rexmpp_disco_info(s))); + rexmpp_iq_reply(s, elem, "result", result); + } else { + rexmpp_log(s, LOG_WARNING, + "Service discovery request for an unknown node: %s", node); + rexmpp_iq_reply(s, elem, "error", + rexmpp_xml_error("cancel", "item-not-found")); + } + if (caps_hash != NULL) { + free(caps_hash); + } + } else if (rexmpp_xml_match(query, "urn:xmpp:ping", "ping")) { + rexmpp_iq_reply(s, elem, "result", NULL); + } else if (rexmpp_xml_match(query, "jabber:iq:version", "query")) { + rexmpp_xml_t *reply = + rexmpp_xml_new_elem("query", "jabber:iq:version"); + rexmpp_xml_t *name = rexmpp_xml_new_elem("name", NULL); + rexmpp_xml_add_text(name, s->client_name); + rexmpp_xml_add_child(reply, name); + rexmpp_xml_t *version = rexmpp_xml_new_elem("version", NULL); + rexmpp_xml_add_text(version, s->client_version); + rexmpp_xml_add_child(reply, version); + rexmpp_iq_reply(s, elem, "result", reply); + } else { + /* An unknown request. */ + rexmpp_iq_reply(s, elem, "error", + rexmpp_xml_error("cancel", "service-unavailable")); } - } else if (rexmpp_xml_match(query, "urn:xmpp:ping", "ping")) { - rexmpp_iq_reply(s, elem, "result", NULL); - } else { - /* An unknown request. */ - rexmpp_iq_reply(s, elem, "error", - rexmpp_xml_error("cancel", "service-unavailable")); } } - free(type); } - /* Stream negotiation, - https://tools.ietf.org/html/rfc6120#section-4.3 */ - if (s->stream_state == REXMPP_STREAM_NEGOTIATION && - rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) { - - /* Remember features. */ - if (s->stream_features != NULL) { - xmlFreeNode(s->stream_features); - } - s->stream_features = xmlCopyNode(elem, 1); - - /* Nothing to negotiate. */ - if (xmlFirstElementChild(elem) == NULL) { - rexmpp_stream_is_ready(s); - return; - } - - /* TODO: check for required features properly here. Currently - assuming that STARTTLS, SASL, and BIND (with an exception for - SM) are always required if they are present. */ - xmlNodePtr child = - rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls", - "starttls"); - if (child != NULL) { - s->stream_state = REXMPP_STREAM_STARTTLS; - xmlNodePtr starttls_cmd = xmlNewNode(NULL, "starttls"); - xmlNewNs(starttls_cmd, "urn:ietf:params:xml:ns:xmpp-tls", NULL); - rexmpp_send(s, starttls_cmd); - return; - } - - child = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl", - "mechanisms"); - if (child != NULL) { - s->stream_state = REXMPP_STREAM_SASL; - s->sasl_state = REXMPP_SASL_NEGOTIATION; - char mech_list[2048]; /* todo: perhaps grow it dynamically */ - mech_list[0] = '\0'; - xmlNodePtr mechanism; - for (mechanism = xmlFirstElementChild(child); - mechanism != NULL; - mechanism = xmlNextElementSibling(mechanism)) { - if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl", - "mechanism")) { - snprintf(mech_list + strlen(mech_list), - 2048 - strlen(mech_list), - "%s ", - xmlNodeGetContent(mechanism)); + /* Incoming presence information. */ + if (rexmpp_xml_match(elem, "jabber:client", "presence") && + s->manage_roster && + s->track_roster_presence) { + const char *from = rexmpp_xml_find_attr_val(elem, "from"); + if (from != NULL) { + struct rexmpp_jid from_jid; + rexmpp_jid_parse(from, &from_jid); + if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) { + /* The bare JID is in the roster. */ + const char *type = rexmpp_xml_find_attr_val(elem, "type"); + rexmpp_xml_t *cur, *prev; + if (type == NULL || strcmp(type, "unavailable") == 0) { + /* Either a new "available" presence or an "unavailable" + one: remove the previously stored presence for this + JID. */ + for (prev = NULL, cur = s->roster_presence; + cur != NULL; + prev = cur, cur = cur->next) { + const char *cur_from = rexmpp_xml_find_attr_val(cur, "from"); + if (strcmp(cur_from, from_jid.full) == 0) { + if (prev == NULL) { + s->roster_presence = cur->next; + } else { + prev->next = cur->next; + } + rexmpp_xml_free(cur); + break; + } + } } - } - const char *mech = - gsasl_client_suggest_mechanism(s->sasl_ctx, mech_list); - rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech); - int sasl_err; - char *sasl_buf; - sasl_err = gsasl_client_start(s->sasl_ctx, mech, &(s->sasl_session)); - if (sasl_err != GSASL_OK) { - rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s", - gsasl_strerror(sasl_err)); - s->sasl_state = REXMPP_SASL_ERROR; - return; - } - sasl_err = gsasl_step64 (s->sasl_session, "", (char**)&sasl_buf); - if (sasl_err != GSASL_OK) { - if (sasl_err == GSASL_NEEDS_MORE) { - rexmpp_log(s, LOG_DEBUG, "SASL needs more data"); - } else { - rexmpp_log(s, LOG_ERR, "SASL error: %s", - gsasl_strerror(sasl_err)); - s->sasl_state = REXMPP_SASL_ERROR; - return; + if (type == NULL) { + /* An "available" presence: add it. */ + rexmpp_xml_t *presence = rexmpp_xml_clone(elem); + presence->next = s->roster_presence; + s->roster_presence = presence; } } - xmlNodePtr auth_cmd = xmlNewNode(NULL, "auth"); - xmlNewProp(auth_cmd, "mechanism", mech); - xmlNewNs(auth_cmd, "urn:ietf:params:xml:ns:xmpp-sasl", NULL); - xmlNodeAddContent(auth_cmd, sasl_buf); - free(sasl_buf); - rexmpp_send(s, auth_cmd); - return; } + } - child = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm"); - if (s->stream_id != NULL && child != NULL) { - s->stream_state = REXMPP_STREAM_SM_RESUME; - char buf[11]; - snprintf(buf, 11, "%u", s->stanzas_in_count); - xmlNodePtr sm_resume = xmlNewNode(NULL, "resume"); - xmlNewNs(sm_resume, "urn:xmpp:sm:3", NULL); - xmlNewProp(sm_resume, "previd", s->stream_id); - xmlNewProp(sm_resume, "h", buf); - rexmpp_send(s, sm_resume); - return; - } + /* Incoming messages. */ + if (rexmpp_xml_match(elem, "jabber:client", "message")) { + const char *from = rexmpp_xml_find_attr_val(elem, "from"); + if (from != NULL) { + struct rexmpp_jid from_jid; + rexmpp_jid_parse(from, &from_jid); + if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL || + strcmp(from_jid.bare, s->assigned_jid.bare) == 0) { + rexmpp_xml_t *event = + rexmpp_xml_find_child(elem, + "http://jabber.org/protocol/pubsub#event", + "event"); + if (event != NULL && s->manage_roster && s->track_roster_events) { + rexmpp_xml_t *items = + rexmpp_xml_find_child(event, + "http://jabber.org/protocol/pubsub#event", + "items"); + if (items != NULL) { + const char *node = rexmpp_xml_find_attr_val(items, "node"); + if (node != NULL) { + /* Remove the previously stored items for the same sender + and node, if any. */ + rexmpp_xml_t *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; + } + rexmpp_xml_free(cur); + cur = NULL; + } + + /* Add the new message. */ + rexmpp_xml_t *message = rexmpp_xml_clone(elem); + message->next = s->roster_events; + s->roster_events = message; - child = - rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind"); - if (child != NULL) { - rexmpp_stream_bind(s); - return; + /* Process the node at once. */ + if (s->retrieve_openpgp_keys && + strcmp(node, "urn:xmpp:openpgp:0:public-keys") == 0) { + rexmpp_openpgp_check_keys(s, from_jid.bare, items); + } + if (s->autojoin_bookmarked_mucs && + strcmp(node, "urn:xmpp:bookmarks:1") == 0 && + strcmp(from_jid.bare, s->assigned_jid.bare) == 0) { + rexmpp_xml_t *item; + for (item = rexmpp_xml_first_elem_child(items); + item != NULL; + item = rexmpp_xml_next_elem_sibling(item)) { + rexmpp_xml_t *conference = + rexmpp_xml_find_child(item, + "urn:xmpp:bookmarks:1", + "conference"); + if (conference == NULL) { + continue; + } + const char *item_id = rexmpp_xml_find_attr_val(item, "id"); + if (item_id == NULL) { + continue; + } + const char *autojoin = + rexmpp_xml_find_attr_val(conference, "autojoin"); + if (autojoin == NULL) { + continue; + } + if (strcmp(autojoin, "true") == 0 || + strcmp(autojoin, "1") == 0) { + rexmpp_xml_t *nick = + rexmpp_xml_find_child(conference, + "urn:xmpp:bookmarks:1", + "nick"); + const char *nick_str = NULL; + if (nick != NULL) { + nick_str = rexmpp_xml_text_child(nick); + } + if (nick_str == NULL) { + nick_str = s->initial_jid.local; + } + char *occupant_jid = + malloc(strlen(item_id) + strlen(nick_str) + 2); + sprintf(occupant_jid, "%s/%s", item_id, nick_str); + const char *password = + rexmpp_xml_find_attr_val(conference, "password"); + rexmpp_muc_join(s, occupant_jid, password, + s->muc_ping_default_delay); + free(occupant_jid); + } + } + } + } + } + } + } } } /* Stream errors, https://tools.ietf.org/html/rfc6120#section-4.9 */ if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "error")) { - rexmpp_log(s, LOG_ERR, "stream error"); - s->stream_state = REXMPP_STREAM_ERROR; - return; + if (rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-streams", + "reset") != NULL || + rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-streams", + "system-shutdown") != NULL) { + rexmpp_log(s, LOG_WARNING, "Server reset or shutdown."); + s->stream_state = REXMPP_STREAM_ERROR_RECONNECT; + return REXMPP_E_AGAIN; + } else { + rexmpp_log(s, LOG_ERR, "Stream error"); + s->stream_state = REXMPP_STREAM_ERROR; + return REXMPP_E_STREAM; + } } /* STARTTLS negotiation, @@ -1609,12 +2187,11 @@ void rexmpp_process_element (rexmpp_t *s) { if (s->stream_state == REXMPP_STREAM_STARTTLS) { if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls", "proceed")) { - rexmpp_tls_start(s); - return; + return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s)); } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls", "failure")) { rexmpp_log(s, LOG_ERR, "STARTTLS failure"); - return; + return REXMPP_E_TLS; } } @@ -1625,46 +2202,36 @@ void rexmpp_process_element (rexmpp_t *s) { int sasl_err; if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "challenge")) { - sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem), - (char**)&sasl_buf); - if (sasl_err != GSASL_OK) { - if (sasl_err == GSASL_NEEDS_MORE) { - rexmpp_log(s, LOG_DEBUG, "SASL needs more data"); - } else { - rexmpp_log(s, LOG_ERR, "SASL error: %s", - gsasl_strerror(sasl_err)); - s->sasl_state = REXMPP_SASL_ERROR; - return; - } + const char *challenge = rexmpp_xml_text_child(elem); + sasl_err = rexmpp_sasl_step64 (s, challenge, (char**)&sasl_buf); + if (sasl_err) { + s->sasl_state = REXMPP_SASL_ERROR; + return REXMPP_E_SASL; } - xmlNodePtr response = xmlNewNode(NULL, "response"); - xmlNewNs(response, "urn:ietf:params:xml:ns:xmpp-sasl", NULL); - xmlNodeAddContent(response, sasl_buf); + rexmpp_xml_t *response = + rexmpp_xml_new_elem("response", "urn:ietf:params:xml:ns:xmpp-sasl"); + rexmpp_xml_add_text(response, sasl_buf); free(sasl_buf); rexmpp_send(s, response); - return; } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "success")) { - sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem), - (char**)&sasl_buf); + const char *success = rexmpp_xml_text_child(elem); + sasl_err = rexmpp_sasl_step64 (s, success, (char**)&sasl_buf); free(sasl_buf); - if (sasl_err == GSASL_OK) { + if (! sasl_err) { rexmpp_log(s, LOG_DEBUG, "SASL success"); } else { - rexmpp_log(s, LOG_ERR, "SASL error: %s", - gsasl_strerror(sasl_err)); s->sasl_state = REXMPP_SASL_ERROR; - return; + return REXMPP_E_SASL; } s->sasl_state = REXMPP_SASL_ACTIVE; - s->stream_state = REXMPP_STREAM_RESTART; - return; + s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser); + return rexmpp_stream_open(s); } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "failure")) { /* todo: would be nice to retry here, but just giving up for now */ rexmpp_log(s, LOG_ERR, "SASL failure"); - rexmpp_stop(s); - return; + return rexmpp_stop(s); } } @@ -1672,20 +2239,23 @@ void rexmpp_process_element (rexmpp_t *s) { if (s->stream_state == REXMPP_STREAM_SM_FULL) { if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "enabled")) { s->sm_state = REXMPP_SM_ACTIVE; - char *resume = xmlGetProp(elem, "resume"); + const char *resume = rexmpp_xml_find_attr_val(elem, "resume"); if (resume != NULL) { if (s->stream_id != NULL) { free(s->stream_id); } - s->stream_id = xmlGetProp(elem, "id"); - xmlFree(resume); + const char *stream_id = rexmpp_xml_find_attr_val(elem, "id"); + s->stream_id = NULL; + if (stream_id != NULL) { + s->stream_id = strdup(stream_id); + } } rexmpp_stream_is_ready(s); } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) { s->stream_state = REXMPP_STREAM_SM_ACKS; s->sm_state = REXMPP_SM_NEGOTIATION; - xmlNodePtr sm_enable = xmlNewNode(NULL, "enable"); - xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL); + rexmpp_xml_t *sm_enable = + rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3"); rexmpp_send(s, sm_enable); } } else if (s->stream_state == REXMPP_STREAM_SM_ACKS) { @@ -1697,8 +2267,8 @@ void rexmpp_process_element (rexmpp_t *s) { } } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) { s->sm_state = REXMPP_SM_INACTIVE; - xmlNodePtr sm_enable = xmlNewNode(NULL, "enable"); - xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL); + rexmpp_xml_t *sm_enable = + rexmpp_xml_new_elem("enable", "urn:xmpp:sm:3"); rexmpp_send(s, sm_enable); } rexmpp_stream_is_ready(s); @@ -1715,17 +2285,16 @@ void rexmpp_process_element (rexmpp_t *s) { while (s->active_iq != NULL) { /* todo: check that those are not queued for resending? */ rexmpp_iq_t *next = s->active_iq->next; - xmlFreeNode(s->active_iq->request); - free(s->active_iq); + rexmpp_iq_t *iq = s->active_iq; s->active_iq = next; + rexmpp_iq_finish(s, iq, 0, NULL); } - xmlNodePtr child = + rexmpp_xml_t *child = rexmpp_xml_find_child(s->stream_features, "urn:ietf:params:xml:ns:xmpp-bind", "bind"); if (child != NULL) { - rexmpp_stream_bind(s); - return; + return rexmpp_stream_bind(s); } } } @@ -1734,74 +2303,80 @@ void rexmpp_process_element (rexmpp_t *s) { s->stanzas_in_count++; } if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "r")) { - rexmpp_sm_ack(s); + return rexmpp_sm_ack(s); } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "a")) { rexmpp_sm_handle_ack(s, elem); } + return REXMPP_SUCCESS; } -void rexmpp_sax_characters (rexmpp_t *s, const char *ch, int len) +/* These SAX handlers are similar to those in rexmpp_xml.c, might be + nice to reuse them. */ +void rexmpp_sax_characters (rexmpp_t *s, const char *ch, size_t len) { if (s->current_element != NULL) { - xmlNodeAddContentLen(s->current_element, ch, len); + rexmpp_xml_t *last_node = s->current_element->alt.elem.children; + if (last_node != NULL && last_node->type == REXMPP_XML_TEXT) { + /* The last child is textual as well, just extend it */ + size_t last_len = strlen(last_node->alt.text); + char *new_alt_text = realloc(last_node->alt.text, last_len + len + 1); + if (new_alt_text == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to reallocate the XML element text buffer: %s", + strerror(errno)); + return; + } + last_node->alt.text = new_alt_text; + strncpy(last_node->alt.text + last_len, ch, len); + last_node->alt.text[last_len + len] = '\0'; + } else { + rexmpp_xml_t *text_node = rexmpp_xml_new_text_len(ch, len); + if (text_node != NULL) { + text_node->next = s->current_element->alt.elem.children; + s->current_element->alt.elem.children = text_node; + } + } } } void rexmpp_sax_start_elem_ns (rexmpp_t *s, - const char *localname, - const char *prefix, - const char *URI, - int nb_namespaces, - const char **namespaces, - int nb_attributes, - int nb_defaulted, - const char **attributes) + const char *name, + const char *namespace, + rexmpp_xml_attr_t *attributes) { - int i; if (s->stream_state == REXMPP_STREAM_OPENING && - strcmp(localname, "stream") == 0 && - strcmp(URI, "http://etherx.jabber.org/streams") == 0) { + s->current_element == NULL && + strcmp(name, "stream") == 0 && + strcmp(namespace, "http://etherx.jabber.org/streams") == 0) { rexmpp_log(s, LOG_DEBUG, "stream start"); s->stream_state = REXMPP_STREAM_NEGOTIATION; + rexmpp_xml_attribute_free_list(attributes); return; } if (s->stream_state != REXMPP_STREAM_OPENING) { if (s->current_element == NULL) { - s->current_element = xmlNewNode(NULL, localname); + s->current_element = rexmpp_xml_new_elem(name, namespace); s->current_element_root = s->current_element; } else { - xmlNodePtr node = xmlNewNode(NULL, localname); - xmlAddChild(s->current_element, node); + rexmpp_xml_t *node = rexmpp_xml_new_elem(name, namespace); + node->next = s->current_element->alt.elem.children; + s->current_element->alt.elem.children = node; s->current_element = node; } - xmlNsPtr ns = xmlNewNs(s->current_element, URI, prefix); - s->current_element->ns = ns; - for (i = 0; i < nb_attributes; i++) { - size_t attr_len = attributes[i * 5 + 4] - attributes[i * 5 + 3]; - char *attr_val = malloc(attr_len + 1); - attr_val[attr_len] = '\0'; - strncpy(attr_val, attributes[i * 5 + 3], attr_len); - xmlNewProp(s->current_element, attributes[i * 5], attr_val); - free(attr_val); - } + s->current_element->alt.elem.attributes = attributes; } } -void rexmpp_sax_end_elem_ns (rexmpp_t *s, - const char *localname, - const char *prefix, - const char *URI) +void rexmpp_sax_end_elem_ns (rexmpp_t *s) { if ((s->stream_state == REXMPP_STREAM_CLOSING || s->stream_state == REXMPP_STREAM_ERROR) && - strcmp(localname, "stream") == 0 && - strcmp(URI, "http://etherx.jabber.org/streams") == 0) { + s->current_element == NULL) { rexmpp_log(s, LOG_DEBUG, "stream end"); if (s->sasl_state == REXMPP_SASL_ACTIVE) { - gsasl_finish(s->sasl_session); - s->sasl_session = NULL; + rexmpp_sasl_ctx_cleanup(s); s->sasl_state = REXMPP_SASL_INACTIVE; } s->stream_state = REXMPP_STREAM_CLOSED; @@ -1818,16 +2393,23 @@ void rexmpp_sax_end_elem_ns (rexmpp_t *s, } if (s->current_element != s->current_element_root) { - s->current_element = s->current_element->parent; + /* Find the parent, set it as current element. */ + rexmpp_xml_t *parent = s->current_element_root; + while (parent->alt.elem.children != s->current_element) { + parent = parent->alt.elem.children; + } + s->current_element = parent; } else { - if (s->xml_in_cb != NULL && s->xml_in_cb(s, s->current_element) != 0) { - rexmpp_log(s, LOG_WARNING, - "Message processing was cancelled by xml_in_cb."); + /* Done parsing this element; reverse all the lists of children + and queue it. */ + rexmpp_xml_reverse_children(s->current_element); + if (s->input_queue == NULL) { + s->input_queue = s->current_element; + s->input_queue_last = s->current_element; } else { - rexmpp_process_element(s); + s->input_queue_last->next = s->current_element; + s->input_queue_last = s->current_element; } - - xmlFreeNode(s->current_element); s->current_element = NULL; s->current_element_root = NULL; } @@ -1841,17 +2423,21 @@ rexmpp_err_t rexmpp_close (rexmpp_t *s) { rexmpp_err_t rexmpp_stop (rexmpp_t *s) { if (s->stream_state == REXMPP_STREAM_READY) { - xmlNodePtr presence = xmlNewNode(NULL, "presence"); - xmlNewProp(presence, "type", "unavailable"); + rexmpp_xml_t *presence = + rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "type", "unavailable"); rexmpp_send(s, presence); } + + s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED; + if (s->sm_state == REXMPP_SM_ACTIVE) { int ret = rexmpp_sm_ack(s); - if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) { + if (ret > REXMPP_E_AGAIN) { return ret; } } - s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED; if (s->send_buffer == NULL) { return rexmpp_close(s); } else { @@ -1860,8 +2446,34 @@ rexmpp_err_t rexmpp_stop (rexmpp_t *s) { } rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { - struct timeval now; - gettimeofday(&now, NULL); + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { + rexmpp_log(s, LOG_ERR, "Failed to get time: %s", strerror(errno)); + return REXMPP_E_OTHER; + } + +#ifdef HAVE_CURL + /* curl may work independently from everything else. */ + int curl_running_handles; + CURLMcode curl_code; + do { + curl_code = curl_multi_perform(s->curl_multi, &curl_running_handles); + } while (curl_code == CURLM_CALL_MULTI_PERFORM); + CURLMsg *cmsg; + int curl_queue; + do { + cmsg = curl_multi_info_read(s->curl_multi, &curl_queue); + if (cmsg != NULL && cmsg->msg == CURLMSG_DONE) { + CURL *e = cmsg->easy_handle; + struct rexmpp_http_upload_task *task; + curl_easy_getinfo(e, CURLINFO_PRIVATE, &task); + rexmpp_log(s, LOG_DEBUG, "%s upload is finished", task->fname); + rexmpp_upload_task_finish(task); + curl_multi_remove_handle(s->curl_multi, e); + curl_easy_cleanup(e); + } + } while (cmsg != NULL); +#endif /* Inactive: start or reconnect. */ if ((s->resolver_state == REXMPP_RESOLVER_NONE || @@ -1874,19 +2486,22 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { if (s->manual_host == NULL) { /* Start by querying SRV records. */ rexmpp_log(s, LOG_DEBUG, "start (or reconnect)"); - size_t srv_query_buf_len = strlen(jid_bare_to_host(s->initial_jid)) + + size_t srv_query_buf_len = strlen(s->initial_jid.domain) + strlen("_xmpps-client._tcp..") + 1; char *srv_query = malloc(srv_query_buf_len); + if (srv_query == NULL) { + return REXMPP_E_MALLOC; + } + s->resolver_state = REXMPP_RESOLVER_SRV; snprintf(srv_query, srv_query_buf_len, - "_xmpps-client._tcp.%s.", jid_bare_to_host(s->initial_jid)); - ares_query(s->resolver_channel, srv_query, - ns_c_in, ns_t_srv, rexmpp_srv_tls_cb, s); + "_xmpps-client._tcp.%s.", s->initial_jid.domain); + rexmpp_dns_resolve(s, srv_query, 33, 1, + "xmpps", rexmpp_srv_cb); snprintf(srv_query, srv_query_buf_len, - "_xmpp-client._tcp.%s.", jid_bare_to_host(s->initial_jid)); - ares_query(s->resolver_channel, srv_query, - ns_c_in, ns_t_srv, rexmpp_srv_cb, s); - s->resolver_state = REXMPP_RESOLVER_SRV; + "_xmpp-client._tcp.%s.", s->initial_jid.domain); + rexmpp_dns_resolve(s, srv_query, 33, 1, + "xmpp", rexmpp_srv_cb); free(srv_query); } else { /* A host is configured manually, connect there. */ @@ -1897,31 +2512,64 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } else { s->tls_state = REXMPP_TLS_INACTIVE; } - rexmpp_start_connecting(s); + rexmpp_err_t err = rexmpp_start_connecting(s); + if (err > REXMPP_E_AGAIN) { + return err; + } } } + /* Don't try to reconnect if a stream is requested to be closed. */ + if (s->tcp_state == REXMPP_TCP_ERROR && + (s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED || + s->stream_state == REXMPP_STREAM_CLOSING)) { + return REXMPP_E_TCP; + } + /* Resolving SRV records. This continues in rexmpp_srv_tls_cb, - rexmpp_srv_cb, and rexmpp_after_srv, possibly leading to - connection initiation. */ - if (s->resolver_state != REXMPP_RESOLVER_NONE && - s->resolver_state != REXMPP_RESOLVER_READY) { - ares_process(s->resolver_channel, read_fds, write_fds); + rexmpp_srv_cb. */ + if (rexmpp_dns_process(s, read_fds, write_fds)) { + return REXMPP_E_DNS; + } + + /* Initiating a connection after SRV resolution. */ + if (s->resolver_state == REXMPP_RESOLVER_READY) { + s->resolver_state = REXMPP_RESOLVER_NONE; + /* todo: sort the records */ + if (s->server_srv == NULL && s->server_srv_tls == NULL) { + /* Failed to resolve anything: a fallback. */ + s->server_host = s->initial_jid.domain; + s->server_port = 5222; + rexmpp_start_connecting(s); + } else { + rexmpp_try_next_host(s); + } } /* Connecting. Continues in rexmpp_process_conn_err, possibly leading to stream opening. */ if (s->tcp_state == REXMPP_TCP_CONNECTING) { - rexmpp_process_conn_err(s, - rexmpp_tcp_conn_proceed(&s->server_connection, - read_fds, write_fds)); + rexmpp_err_t err = + rexmpp_process_conn_err(s, + rexmpp_tcp_conn_proceed(s, &s->server_connection, + read_fds, write_fds)); + if (err > REXMPP_E_AGAIN) { + return err; + } } /* SOCKS5 connection. */ if (s->tcp_state == REXMPP_TCP_SOCKS) { - rexmpp_process_socks_err(s, rexmpp_socks_proceed(&s->server_socks_conn)); + rexmpp_err_t err = + rexmpp_process_socks_err(s, rexmpp_socks_proceed(&s->server_socks_conn)); + if (err > REXMPP_E_AGAIN) { + return err; + } } + /* Jingle activity. */ + rexmpp_jingle_run(s, read_fds, write_fds); + /* The things we do while connected. */ /* Sending queued data. */ @@ -1934,22 +2582,62 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { s->stream_state != REXMPP_STREAM_ERROR) && s->sasl_state != REXMPP_SASL_ERROR && s->send_buffer != NULL) { - rexmpp_send_continue(s); + rexmpp_err_t err = rexmpp_send_continue(s); + if (err > REXMPP_E_AGAIN) { + return err; + } } + /* MUC self-pinging. */ + if (s->tcp_state == REXMPP_TCP_CONNECTED && + s->stream_state == REXMPP_STREAM_READY) + { + rexmpp_muc_ping_t *mp = s->muc_ping; + while (mp != NULL) { + if (mp->last_activity.tv_sec + mp->delay <= now.tv_sec) { + if (mp->requested == 0) { + clock_gettime(CLOCK_MONOTONIC, &(mp->last_activity)); + mp->requested = 1; + rexmpp_xml_t *ping_cmd = + rexmpp_xml_new_elem("ping", "urn:xmpp:ping"); + rexmpp_iq_new(s, "get", mp->jid, + ping_cmd, rexmpp_muc_pong, mp); + } else { + /* Requested already, and delay time passed again, without + a reply (not even an error). Warn the user, remove this + MUC. */ + char *occupant_jid_to_remove = mp->jid; + rexmpp_log(s, LOG_WARNING, + "No MUC self-ping reply for %s, " + "disabling self-ping for it", + mp->jid); + mp = mp->next; + rexmpp_muc_ping_remove(s, occupant_jid_to_remove); + continue; + } + } + mp = mp->next; + } + } + /* Pinging the server. */ if (s->tcp_state == REXMPP_TCP_CONNECTED && - s->last_network_activity + s->ping_delay <= time(NULL)) { + s->stream_state == REXMPP_STREAM_READY && + s->last_network_activity.tv_sec + s->ping_delay <= now.tv_sec) { if (s->ping_requested == 0) { s->ping_requested = 1; - xmlNodePtr ping_cmd = xmlNewNode(NULL, "ping"); - xmlNewNs(ping_cmd, "urn:xmpp:ping", NULL); - rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid), - ping_cmd, rexmpp_pong); + rexmpp_xml_t *ping_cmd = + rexmpp_xml_new_elem("ping", "urn:xmpp:ping"); + rexmpp_iq_new(s, "get", s->initial_jid.domain, + ping_cmd, rexmpp_pong, NULL); } else { + /* Last network activity is updated on sending as well as on + receiving, so this will not be triggered right after sending + the request. */ rexmpp_log(s, LOG_WARNING, "Ping timeout, reconnecting."); rexmpp_cleanup(s); rexmpp_schedule_reconnect(s); + return REXMPP_E_AGAIN; } } @@ -1962,32 +2650,30 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { s->stream_state != REXMPP_STREAM_CLOSED && s->stream_state != REXMPP_STREAM_ERROR) && s->sasl_state != REXMPP_SASL_ERROR) { - rexmpp_recv(s); + rexmpp_err_t err = rexmpp_recv(s); + if (err > REXMPP_E_AGAIN) { + return err; + } } /* Performing a TLS handshake. A stream restart happens after this, if everything goes well. */ if (s->tcp_state == REXMPP_TCP_CONNECTED && s->tls_state == REXMPP_TLS_HANDSHAKE) { - rexmpp_tls_handshake(s); - } - - /* Restarting a stream if needed after the above actions. Since it - involves resetting the parser, functions called by that parser - can't do it on their own. */ - if (s->tcp_state == REXMPP_TCP_CONNECTED && - (s->tls_state == REXMPP_TLS_ACTIVE || - s->tls_state == REXMPP_TLS_INACTIVE) && - s->stream_state == REXMPP_STREAM_RESTART) { - xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8"); - rexmpp_stream_open(s); + rexmpp_err_t err = rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s)); + if (err > REXMPP_E_AGAIN) { + return err; + } } /* Closing the stream once everything is sent. */ if (s->tcp_state == REXMPP_TCP_CONNECTED && s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED && s->send_buffer == NULL) { - rexmpp_close(s); + rexmpp_err_t err = rexmpp_close(s); + if (err > REXMPP_E_AGAIN) { + return err; + } } /* Closing TLS and TCP connections once stream is closed. If @@ -1996,31 +2682,48 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { if (s->tcp_state == REXMPP_TCP_CONNECTED && s->stream_state == REXMPP_STREAM_CLOSED && s->tls_state == REXMPP_TLS_CLOSING) { - int ret = gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR); - if (ret == GNUTLS_E_SUCCESS) { + rexmpp_tls_err_t err = rexmpp_tls_disconnect(s, s->tls); + if (err == REXMPP_TLS_SUCCESS) { + rexmpp_log(s, LOG_DEBUG, "TLS disconnected"); s->tls_state = REXMPP_TLS_INACTIVE; rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_CLOSED; + } else if (err != REXMPP_TLS_E_AGAIN) { + s->tls_state = REXMPP_TLS_ERROR; + return REXMPP_E_TLS; } } - if (s->tcp_state == REXMPP_TCP_CLOSED) { + if (s->tcp_state == REXMPP_TCP_CLOSED && + s->stream_state != REXMPP_STREAM_ERROR_RECONNECT) { + rexmpp_console_on_run(s, REXMPP_SUCCESS); return REXMPP_SUCCESS; } else { + rexmpp_console_on_run(s, REXMPP_E_AGAIN); return REXMPP_E_AGAIN; } } -int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { - int conn_fd, max_fd = 0; +int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { + int conn_fd, tls_fd, jingle_fd, max_fd = 0; - if (s->resolver_state != REXMPP_RESOLVER_NONE && - s->resolver_state != REXMPP_RESOLVER_READY) { - max_fd = ares_fds(s->resolver_channel, read_fds, write_fds); + max_fd = rexmpp_dns_fds(s, read_fds, write_fds); + + jingle_fd = rexmpp_jingle_fds(s, read_fds, write_fds); + if (jingle_fd > max_fd) { + max_fd = jingle_fd; + } + +#ifdef HAVE_CURL + int curl_fd; + curl_multi_fdset(s->curl_multi, read_fds, write_fds, NULL, &curl_fd); + if (curl_fd >= max_fd) { + max_fd = curl_fd + 1; } +#endif if (s->tcp_state == REXMPP_TCP_CONNECTING) { - conn_fd = rexmpp_tcp_conn_fds(&s->server_connection, read_fds, write_fds); + conn_fd = rexmpp_tcp_conn_fds(s, &s->server_connection, read_fds, write_fds); if (conn_fd > max_fd) { max_fd = conn_fd; } @@ -2038,13 +2741,9 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } if (s->tls_state == REXMPP_TLS_HANDSHAKE) { - if (gnutls_record_get_direction(s->gnutls_session) == 0) { - FD_SET(s->server_socket, read_fds); - } else { - FD_SET(s->server_socket, write_fds); - } - if (s->server_socket + 1 > max_fd) { - max_fd = s->server_socket + 1; + tls_fd = rexmpp_tls_fds(s, read_fds, write_fds); + if (tls_fd > max_fd) { + max_fd = tls_fd; } } @@ -2061,38 +2760,73 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { return max_fd; } -struct timeval *rexmpp_timeout (rexmpp_t *s, - struct timeval *max_tv, - struct timeval *tv) +struct timespec *rexmpp_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv) { - struct timeval *ret = max_tv; + struct timespec *ret = max_tv; if (s->resolver_state != REXMPP_RESOLVER_NONE && s->resolver_state != REXMPP_RESOLVER_READY) { - ret = ares_timeout(s->resolver_channel, max_tv, tv); + } else if (s->tcp_state == REXMPP_TCP_CONNECTING) { - ret = rexmpp_tcp_conn_timeout(&s->server_connection, max_tv, tv); + ret = rexmpp_tcp_conn_timeout(s, &s->server_connection, max_tv, tv); } - struct timeval now; - gettimeofday(&now, NULL); + + ret = rexmpp_jingle_timeout(s, ret, tv); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); if (s->reconnect_number > 0 && s->next_reconnect_time.tv_sec > now.tv_sec && (ret == NULL || s->next_reconnect_time.tv_sec - now.tv_sec < ret->tv_sec)) { tv->tv_sec = s->next_reconnect_time.tv_sec - now.tv_sec; - tv->tv_usec = 0; + tv->tv_nsec = 0; ret = tv; } if (s->tcp_state == REXMPP_TCP_CONNECTED && - s->last_network_activity + s->ping_delay > now.tv_sec) { - time_t next_ping = s->last_network_activity + s->ping_delay - now.tv_sec; + s->stream_state == REXMPP_STREAM_READY) { + rexmpp_muc_ping_t *mp = s->muc_ping; + while (mp != NULL) { + if (mp->last_activity.tv_sec + mp->delay > now.tv_sec) { + time_t next_ping = + mp->last_activity.tv_sec + mp->delay - now.tv_sec; + if (ret == NULL || next_ping < ret->tv_sec) { + tv->tv_sec = next_ping; + tv->tv_nsec = 0; + ret = tv; + } + } + mp = mp->next; + } + } + + if (s->tcp_state == REXMPP_TCP_CONNECTED && + s->stream_state == REXMPP_STREAM_READY && + s->last_network_activity.tv_sec + s->ping_delay > now.tv_sec) { + time_t next_ping = + s->last_network_activity.tv_sec + s->ping_delay - now.tv_sec; if (ret == NULL || next_ping < ret->tv_sec) { tv->tv_sec = next_ping; - tv->tv_usec = 0; + tv->tv_nsec = 0; ret = tv; } } +#ifdef HAVE_CURL + long curl_timeout; /* in milliseconds */ + curl_multi_timeout(s->curl_multi, &curl_timeout); + if (curl_timeout >= 0 && + (curl_timeout / 1000 < ret->tv_sec || + (curl_timeout / 1000 == ret->tv_sec && + (curl_timeout % 1000) * 1000000 < ret->tv_nsec))) { + tv->tv_sec = curl_timeout / 1000; + tv->tv_nsec = (curl_timeout % 1000) * 1000000; + ret = tv; + } +#endif + return ret; } |