diff options
Diffstat (limited to 'src')
42 files changed, 7956 insertions, 2361 deletions
diff --git a/src/Cargo.toml b/src/Cargo.toml new file mode 100644 index 0000000..65a0ff4 --- /dev/null +++ b/src/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rexmpp_rust" +version = "0.1.0" +authors = ["defanor <defanor@uberspace.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "rexmpp_rust" +crate-type = ["staticlib"] +path = "rexmpp_rust.rs" + +[dependencies] +libc = "0.2" +errno = "0.3" +rxml = "0.9" diff --git a/src/Makefile.am b/src/Makefile.am index 9fff4c8..2dfc581 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,18 +1,8 @@ -AM_CFLAGS = -Werror -Wall -Wextra -pedantic -std=gnu99 \ - -Wno-pointer-sign - -# -Wno-pointer-sign is used to suppress libxml2-related warnings. -# Since we only care about UTF-8, and in almost all cases just its -# ASCII subset (comparing or setting fixed namespaces, element names, -# etc), it shouldn't matter. Later it would be nice to abstract XML -# manipulations anyway, to allow libexpat as an alternative. - +AM_CFLAGS = -Werror -Wall -Wextra -pedantic -std=gnu99 lib_LTLIBRARIES = librexmpp.la librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \ - rexmpp_tcp.h rexmpp_tcp.c \ - rexmpp_socks.h rexmpp_socks.c \ rexmpp.h rexmpp.c \ rexmpp_dns.h rexmpp_dns.c \ rexmpp_tls.h rexmpp_tls.c \ @@ -23,17 +13,43 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \ rexmpp_http_upload.h rexmpp_http_upload.c \ rexmpp_jingle.h rexmpp_jingle.c \ rexmpp_base64.h rexmpp_base64.c \ - rexmpp_sasl.h rexmpp_sasl.c + rexmpp_sasl.h rexmpp_sasl.c \ + rexmpp_xml.h rexmpp_xml.c \ + rexmpp_utf8.h \ + rexmpp_random.h rexmpp_random.c \ + rexmpp_digest.h rexmpp_digest.c + include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \ rexmpp_dns.h rexmpp_tls.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h \ rexmpp_pubsub.h rexmpp_http_upload.h rexmpp_jingle.h rexmpp_base64.h \ - rexmpp_sasl.h -librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) \ + rexmpp_sasl.h rexmpp_xml.h rexmpp_utf8.h rexmpp_xml_parser.h \ + rexmpp_random.h rexmpp_digest.h +librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML2_CFLAGS) $(EXPAT_CFLAGS) \ $(GNUTLS_CFLAGS) $(LIBDANE_CFLAGS) $(OPENSSL_CFLAGS) \ $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(CARES_CFLAGS) $(GPGME_CFLAGS) \ $(ICU_I18N_CFLAGS) $(LIBGCRYPT_CFLAGS) $(CURL_CFLAGS) \ - $(NICE_CFLAGS) $(GLIB_CFLAGS) $(SRTP_CFLAGS) -librexmpp_la_LIBADD = $(LIBXML_LIBS) \ + $(NICE_CFLAGS) $(GLIB_CFLAGS) $(SRTP_CFLAGS) \ + $(PORTAUDIO_CFLAGS) $(OPUS_CFLAGS) $(NETTLE_CFLAGS) +librexmpp_la_LIBADD = $(LIBXML2_LIBS) $(EXPAT_LIBS) \ $(GNUTLS_LIBS) $(LIBDANE_LIBS) $(OPENSSL_LIBS) \ $(GSASL_LIBS) $(UNBOUND_LIBS) $(CARES_LIBS) $(GPGME_LIBS) $(ICU_I18N_LIBS) \ - $(LIBGCRYPT_LIBS) $(CURL_LIBS) $(NICE_LIBS) $(GLIB_LIBS) $(SRTP_LIBS) + $(LIBGCRYPT_LIBS) $(CURL_LIBS) $(NICE_LIBS) $(GLIB_LIBS) $(SRTP_LIBS) \ + $(PORTAUDIO_LIBS) $(OPUS_LIBS) $(NETTLE_LIBS) +librexmpp_la_LDFLAGS = [] + +if USE_RUST +target_debug_librexmpp_rust_a_SOURCES = \ + rexmpp_rust.rs rexmpp.rs rexmpp_jid.rs rexmpp_dns.rs rexmpp_tcp.rs \ + rexmpp_socks.rs rexmpp_xml.rs rexmpp_xml_parser.rs +noinst_LIBRARIES = target/debug/librexmpp_rust.a +librexmpp_la_LIBADD += target/debug/librexmpp_rust.a +librexmpp_la_LDFLAGS += -L. -lpthread -ldl + +target/debug/librexmpp_rust.a: $(target_debug_librexmpp_rust_a_SOURCES) + $(CARGO) build + +else +librexmpp_la_SOURCES += rexmpp_tcp.h rexmpp_tcp.c \ + rexmpp_socks.h rexmpp_socks.c \ + rexmpp_xml_parser.h rexmpp_xml_parser.c +endif diff --git a/src/rexmpp.c b/src/rexmpp.c index d7e1364..24b9965 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -14,13 +14,17 @@ #include <arpa/nameser.h> #include <sys/types.h> #include <sys/socket.h> +#include <assert.h> +#include <stdbool.h> #include "config.h" +#ifdef HAVE_GCRYPT #include <gcrypt.h> -#include <libxml/tree.h> -#include <libxml/xmlsave.h> +#endif +#ifdef USE_UNBOUND #include <unbound.h> +#endif #ifdef HAVE_GPGME #include <gpgme.h> #endif @@ -29,6 +33,7 @@ #endif #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_tcp.h" #include "rexmpp_socks.h" #include "rexmpp_roster.h" @@ -40,6 +45,8 @@ #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; @@ -88,21 +95,13 @@ const char *rexmpp_strerror (rexmpp_err_t 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, ...) { @@ -114,157 +113,225 @@ 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; -} -char *rexmpp_capabilities_hash (rexmpp_t *s, - xmlNodePtr info) -{ - char *out = NULL; - size_t out_len = 0; - char *str = rexmpp_capabilities_string(s, info); - if (str != NULL) { - unsigned int sha1_len = gcry_md_get_algo_dlen(GCRY_MD_SHA1); - char *sha1 = malloc(sha1_len); - if (sha1 != NULL) { - gcry_md_hash_buffer(GCRY_MD_SHA1, sha1, str, strlen(str)); - rexmpp_base64_to(sha1, sha1_len, &out, &out_len); - free(sha1); - } - free(str); - } - return out; + 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; } -xmlNodePtr rexmpp_find_event (rexmpp_t *s, - const char *from, - const char *node, - xmlNodePtr *prev_event) +rexmpp_xml_t *rexmpp_find_event (rexmpp_t *s, + const char *from, + const char *node, + rexmpp_xml_t **prev_event) { - xmlNodePtr prev, cur; + rexmpp_xml_t *prev, *cur; for (prev = NULL, cur = s->roster_events; cur != NULL; - prev = cur, cur = xmlNextElementSibling(cur)) { - char *cur_from = xmlGetProp(cur, "from"); + prev = cur, cur = cur->next) { + const char *cur_from = rexmpp_xml_find_attr_val(cur, "from"); if (cur_from == NULL) { continue; } - xmlNodePtr cur_event = + rexmpp_xml_t *cur_event = rexmpp_xml_find_child(cur, "http://jabber.org/protocol/pubsub#event", "event"); - xmlNodePtr cur_items = + rexmpp_xml_t *cur_items = rexmpp_xml_find_child(cur_event, "http://jabber.org/protocol/pubsub#event", "items"); if (cur_items == NULL) { - free(cur_from); continue; } - char *cur_node = xmlGetProp(cur_items, "node"); + 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); - free(cur_node); - free(cur_from); if (match) { if (prev_event != NULL) { *prev_event = prev; @@ -282,36 +349,39 @@ char *rexmpp_get_name (rexmpp_t *s, const char *jid_str) { return NULL; } if (s->manage_roster) { - xmlNodePtr roster_item = rexmpp_roster_find_item(s, jid.bare, NULL); - if (roster_item) { - char *name = xmlGetProp(roster_item, "name"); + 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 name; + return strdup(name); } } if (s->track_roster_events) { - xmlNodePtr elem = + rexmpp_xml_t *elem = rexmpp_find_event(s, jid.bare, "http://jabber.org/protocol/nick", NULL); if (elem != NULL) { - xmlNodePtr event = + rexmpp_xml_t *event = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/pubsub#event", "event"); - xmlNodePtr items = + rexmpp_xml_t *items = rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event", "items"); - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event", "item"); if (item != NULL) { - xmlNodePtr nick = + rexmpp_xml_t *nick = rexmpp_xml_find_child(item, "http://jabber.org/protocol/nick", "nick"); - if (nick != NULL) { - return xmlNodeGetContent(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)); } } } @@ -323,93 +393,75 @@ char *rexmpp_get_name (rexmpp_t *s, const char *jid_str) { 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_new_node (const char *name, const char *namespace) { - xmlNodePtr node = xmlNewNode(NULL, name); - xmlNewNs(node, namespace, NULL); - return node; -} - -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, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { struct rexmpp_feature_search *search = ptr; if (! success) { - char *to = xmlGetProp(request, "to"); - xmlNodePtr query = xmlFirstElementChild(request); - rexmpp_log(s, LOG_ERR, "Failed to query %s for %s.", to, query->nsDef->href); - free(to); + 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) { - xmlNodePtr query = xmlFirstElementChild(response); + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(response); if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) { - xmlNodePtr child = xmlFirstElementChild(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")) { - char *var = xmlGetProp(child, "var"); + 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; } - free(var); } } - child = child->next; + child = rexmpp_xml_next_elem_sibling(child); } if ((! search->found) && (search->max_requests > 0)) { /* Still not found, request items */ - char *jid = xmlGetProp(request, "to"); + const char *jid = rexmpp_xml_find_attr_val(request, "to"); if (jid != NULL) { search->pending++; search->max_requests--; - xmlNodePtr query = - rexmpp_xml_new_node("query", + 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); - free(jid); } } } else if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#items", "query")) { - xmlNodePtr child = xmlFirstElementChild(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")) { - char *jid = xmlGetProp(child, "jid"); + const char *jid = rexmpp_xml_find_attr_val(child, "jid"); if (jid != NULL) { search->pending++; search->max_requests--; - xmlNodePtr query = - rexmpp_xml_new_node("query", + 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 = child->next; + child = rexmpp_xml_next_elem_sibling(child); } } } @@ -433,6 +485,9 @@ rexmpp_disco_find_feature (rexmpp_t *s, { 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; @@ -440,8 +495,8 @@ rexmpp_disco_find_feature (rexmpp_t *s, search->cb_data = cb_data; search->fresh = fresh; search->feature_var = feature_var; - xmlNodePtr query = - rexmpp_xml_new_node("query", "http://jabber.org/protocol/disco#info"); + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info"); if (jid == NULL) { jid = s->initial_jid.domain; } @@ -449,18 +504,18 @@ rexmpp_disco_find_feature (rexmpp_t *s, rexmpp_disco_find_feature_cb, search, fresh); } -xmlNodePtr rexmpp_disco_info (rexmpp_t *s) { +rexmpp_xml_t *rexmpp_disco_info (rexmpp_t *s) { if (s->disco_info != NULL) { return s->disco_info; } - xmlNodePtr prev = NULL, cur; + 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. */ - s->disco_info = xmlNewNode(NULL, "identity"); - xmlNewProp(s->disco_info, "category", "client"); - xmlNewProp(s->disco_info, "type", s->client_type); - xmlNewProp(s->disco_info, "name", s->client_name); + 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; @@ -511,17 +566,17 @@ xmlNodePtr rexmpp_disco_info (rexmpp_t *s) { return s->disco_info; } +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, 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; @@ -532,29 +587,30 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, 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->manage_roster = 1; + s->enable_carbons = true; + s->manage_roster = true; s->roster_cache_file = NULL; - s->track_roster_presence = 1; - s->track_roster_events = 1; - s->nick_notifications = 1; + s->track_roster_presence = true; + s->track_roster_events = true; + s->nick_notifications = true; #ifdef HAVE_GPGME - s->retrieve_openpgp_keys = 1; + s->retrieve_openpgp_keys = true; #else - s->retrieve_openpgp_keys = 0; + s->retrieve_openpgp_keys = false; #endif - s->autojoin_bookmarked_mucs = 1; + s->autojoin_bookmarked_mucs = true; s->tls_policy = REXMPP_TLS_REQUIRE; - s->enable_jingle = 1; + 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 = 1; + s->jingle_prefer_rtcp_mux = true; + s->muc_ping_default_delay = 600; s->send_buffer = NULL; s->send_queue = NULL; s->server_srv = NULL; @@ -562,6 +618,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->server_srv_tls = 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; @@ -577,7 +634,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->iq_cache = NULL; s->reconnect_number = 0; s->next_reconnect_time.tv_sec = 0; - s->next_reconnect_time.tv_usec = 0; + s->next_reconnect_time.tv_nsec = 0; s->initial_jid.full[0] = '\0'; s->assigned_jid.full[0] = '\0'; s->stanza_queue_size = 1024; @@ -585,52 +642,50 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->iq_queue_size = 1024; 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; - /* The default description. Since the players and streamers are - external for now, this may be adjusted by an application or a - user. */ s->jingle_rtp_description = - rexmpp_xml_new_node("description", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(s->jingle_rtp_description, "media", "audio"); - xmlNodePtr pl_type; - - pl_type = rexmpp_xml_new_node("payload-type", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(pl_type, "id", "97"); - xmlNewProp(pl_type, "name", "opus"); - xmlNewProp(pl_type, "clockrate", "48000"); - xmlNewProp(pl_type, "channels", "2"); - xmlAddChild(s->jingle_rtp_description, pl_type); - - pl_type = rexmpp_xml_new_node("payload-type", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(pl_type, "id", "96"); - xmlNewProp(pl_type, "name", "speex"); - xmlNewProp(pl_type, "clockrate", "32000"); - xmlNewProp(pl_type, "channels", "1"); - xmlAddChild(s->jingle_rtp_description, pl_type); - - pl_type = rexmpp_xml_new_node("payload-type", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(pl_type, "id", "0"); - xmlNewProp(pl_type, "name", "PCMU"); - xmlNewProp(pl_type, "clockrate", "8000"); - xmlNewProp(pl_type, "channels", "1"); - xmlAddChild(s->jingle_rtp_description, pl_type); - - pl_type = rexmpp_xml_new_node("payload-type", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(pl_type, "id", "8"); - xmlNewProp(pl_type, "name", "PCMA"); - xmlNewProp(pl_type, "clockrate", "8000"); - xmlNewProp(pl_type, "channels", "1"); - xmlAddChild(s->jingle_rtp_description, pl_type); + 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."); @@ -646,6 +701,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, return REXMPP_E_JID; } +#ifdef HAVE_GCRYPT if (! gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { rexmpp_log(s, LOG_DEBUG, "Initializing libgcrypt"); if (gcry_check_version(NULL) == NULL) { @@ -654,8 +710,9 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, } gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } +#endif - s->xml_parser = xmlCreatePushParserCtxt(&sax, s, "", 0, NULL); + 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."); @@ -663,13 +720,13 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, } if (rexmpp_dns_ctx_init(s)) { - xmlFreeParserCtxt(s->xml_parser); + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_DNS; } if (rexmpp_tls_init(s)) { rexmpp_dns_ctx_deinit(s); - xmlFreeParserCtxt(s->xml_parser); + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_TLS; } @@ -677,7 +734,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, if (err) { rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); - xmlFreeParserCtxt(s->xml_parser); + rexmpp_xml_parser_free(s->xml_parser); return REXMPP_E_SASL; } @@ -685,7 +742,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, rexmpp_sasl_ctx_deinit(s); rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); - xmlFreeParserCtxt(s->xml_parser); + rexmpp_xml_parser_free(s->xml_parser); } #ifdef HAVE_GPGME @@ -698,9 +755,11 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); rexmpp_jingle_stop(s); - xmlFreeParserCtxt(s->xml_parser); + 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) { @@ -711,6 +770,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, 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; @@ -729,6 +790,7 @@ void rexmpp_cleanup (rexmpp_t *s) { 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; @@ -743,20 +805,20 @@ 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; } if (s->send_queue != NULL) { - xmlFreeNodeList(s->send_queue); + 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) { - xmlFreeNodeList(s->input_queue); + rexmpp_xml_free_list(s->input_queue); s->input_queue = NULL; s->input_queue_last = NULL; } @@ -777,12 +839,12 @@ void rexmpp_cleanup (rexmpp_t *s) { void rexmpp_iq_finish (rexmpp_t *s, rexmpp_iq_t *iq, int success, - xmlNodePtr response) + rexmpp_xml_t *response) { if (iq->cb != NULL) { iq->cb(s, iq->cb_data, iq->request, response, success); } - xmlFreeNode(iq->request); + rexmpp_xml_free(iq->request); free(iq); } @@ -800,9 +862,9 @@ void rexmpp_done (rexmpp_t *s) { rexmpp_sasl_ctx_deinit(s); rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); - xmlFreeParserCtxt(s->xml_parser); + rexmpp_xml_parser_free(s->xml_parser); if (s->jingle_rtp_description != NULL) { - xmlFreeNode(s->jingle_rtp_description); + rexmpp_xml_free(s->jingle_rtp_description); s->jingle_rtp_description = NULL; } if (s->stream_id != NULL) { @@ -810,27 +872,33 @@ void rexmpp_done (rexmpp_t *s) { 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) { - xmlFreeNodeList(s->roster_presence); + rexmpp_xml_free_list(s->roster_presence); s->roster_presence = NULL; } if (s->roster_events != NULL) { - xmlFreeNodeList(s->roster_events); + 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; } if (s->stanza_queue != NULL) { - xmlFreeNodeList(s->stanza_queue); + rexmpp_xml_free_list(s->stanza_queue); s->stanza_queue = NULL; } while (s->active_iq != NULL) { @@ -840,7 +908,7 @@ void rexmpp_done (rexmpp_t *s) { rexmpp_iq_finish(s, iq, 0, NULL); } if (s->iq_cache != NULL) { - xmlFreeNodeList(s->iq_cache); + rexmpp_xml_free_list(s->iq_cache); s->iq_cache = NULL; } } @@ -853,7 +921,7 @@ void rexmpp_schedule_reconnect (rexmpp_t *s) { return; } if (s->reconnect_number == 0) { - gcry_create_nonce((char*)&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; } @@ -866,7 +934,7 @@ void rexmpp_schedule_reconnect (rexmpp_t *s) { 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, @@ -884,96 +952,7 @@ const char *jid_bare_to_host (const char *jid_bare) { return NULL; } -char *rexmpp_gen_id (rexmpp_t *s) { - (void)s; - char buf_raw[18], *buf_base64 = NULL; - size_t buf_base64_len = 0; - gcry_create_nonce(buf_raw, 18); - rexmpp_base64_to(buf_raw, 18, &buf_base64, &buf_base64_len); - return buf_base64; -} - -xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node) { - char *buf = rexmpp_gen_id(s); - if (buf == NULL) { - return NULL; - } - xmlNewProp(node, "id", buf); - free(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->nsDef == NULL || node->nsDef->href == NULL) { - if (strcmp(namespace, "jabber:client") != 0) { - return 0; - } - } else { - if (node->nsDef) { - if (strcmp(namespace, node->nsDef->href) != 0) { - return 0; - } - } else { - if (namespace != NULL) { - return 0; - } - } - } - } - return 1; -} - -int rexmpp_xml_eq (xmlNodePtr n1, xmlNodePtr n2) { - /* Just serialize and compare strings for now: awkward, but - simple. */ - char *n1str = rexmpp_xml_serialize(n1); - char *n2str = rexmpp_xml_serialize(n2); - int eq = (strcmp(n1str, n2str) == 0); - free(n1str); - free(n2str); - return eq; -} - -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; } @@ -982,42 +961,15 @@ xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) { struct tm utc_time; gmtime_r(&t, &utc_time); strftime(buf, 42, "%FT%TZ", &utc_time); - xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL); - xmlNewProp(delay, "stamp", buf); + 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]) { - xmlNewProp(delay, "from", s->assigned_jid.full); + rexmpp_xml_add_attr(delay, "from", s->assigned_jid.full); } return node; } -xmlNodePtr rexmpp_xml_parse (const char *str, int str_len) { - xmlNodePtr elem = NULL; - xmlDocPtr doc = xmlReadMemory(str, str_len, "", "utf-8", XML_PARSE_NONET); - if (doc != NULL) { - elem = xmlCopyNode(xmlDocGetRootElement(doc), 1); - xmlFreeDoc(doc); - } - return elem; -} - -char *rexmpp_xml_serialize (xmlNodePtr node) { - xmlBufferPtr buf = xmlBufferCreate(); - xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0); - xmlSaveTree(ctx, node); - xmlSaveFlush(ctx); - xmlSaveClose(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; @@ -1052,61 +1004,64 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s) } ssize_t ret; rexmpp_tls_err_t err; - int tls_was_active; while (1) { - tls_was_active = (s->tls_state == REXMPP_TLS_ACTIVE); - if (tls_was_active) { + if (s->tls_state == REXMPP_TLS_ACTIVE) { err = rexmpp_tls_send (s, + s->tls, s->send_buffer, s->send_buffer_len, &ret); - } 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_SUCCESS) { - return ret; - } - s->send_queue = xmlNextElementSibling(s->send_queue); - xmlFreeNode(node); - } else { - return REXMPP_SUCCESS; - } - } - } else { - if (tls_was_active) { + if (ret <= 0) { if (err != REXMPP_TLS_E_AGAIN) { s->tls_state = REXMPP_TLS_ERROR; /* Assume a TCP error for now as well. */ rexmpp_cleanup(s); s->tcp_state = REXMPP_TCP_ERROR; rexmpp_schedule_reconnect(s); - return REXMPP_E_AGAIN; } - } 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_AGAIN; } + /* E_AGAIN, similarly to the TLS case. */ + return REXMPP_E_AGAIN; } - 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; + } + } + } } @@ -1121,19 +1076,19 @@ rexmpp_err_t rexmpp_send_raw (rexmpp_t *s, const void *data, size_t data_len) 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; } @@ -1144,20 +1099,21 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node) if (s->sm_state == REXMPP_SM_ACTIVE) { if (s->stanzas_out_count >= s->stanza_queue_size + s->stanzas_out_acknowledged) { - xmlFreeNode(node); + 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) { @@ -1166,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; } @@ -1177,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; } @@ -1192,28 +1148,25 @@ 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.full[0]) { - xmlNewProp(iq_stanza, "from", s->assigned_jid.full); + 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); } @@ -1221,7 +1174,7 @@ void rexmpp_iq_reply (rexmpp_t *s, rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, const char *type, const char *to, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t cb, void *cb_data) { @@ -1232,24 +1185,30 @@ rexmpp_err_t 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; 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.full[0]) { - xmlNewProp(iq_stanza, "from", s->assigned_jid.full); + 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; @@ -1259,12 +1218,12 @@ rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, void rexmpp_iq_cache_cb (rexmpp_t *s, void *cb_data, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { if (success && response != NULL) { - xmlNodePtr prev_last = NULL, last = NULL, ciq = s->iq_cache; + 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; @@ -1273,12 +1232,12 @@ void rexmpp_iq_cache_cb (rexmpp_t *s, ciq = ciq->next->next; } if (size >= s->iq_queue_size && prev_last != NULL) { - xmlFreeNode(last->next); - xmlFreeNode(last); + rexmpp_xml_free(last->next); + rexmpp_xml_free(last); prev_last->next->next = NULL; } - xmlNodePtr req = xmlCopyNode(request, 1); - xmlNodePtr resp = xmlCopyNode(response, 1); + 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; @@ -1293,24 +1252,22 @@ void rexmpp_iq_cache_cb (rexmpp_t *s, rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, const char *type, const char *to, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t cb, void *cb_data, int fresh) { if (! fresh) { - xmlNodePtr ciq = s->iq_cache; + rexmpp_xml_t *ciq = s->iq_cache; while (ciq != NULL && ciq->next != NULL) { - xmlNodePtr ciq_pl = xmlFirstElementChild(ciq); - char *ciq_type = xmlGetProp(ciq, "type"); - char *ciq_to = xmlGetProp(ciq, "to"); + 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); - free(ciq_to); - free(ciq_type); if (matches) { - xmlFreeNode(payload); + rexmpp_xml_free(payload); if (cb != NULL) { cb(s, cb_data, ciq, ciq->next, 1); } @@ -1328,24 +1285,23 @@ rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, 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); } -rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem); +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; @@ -1355,12 +1311,12 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) { do { tls_was_active = (s->tls_state == REXMPP_TLS_ACTIVE); if (tls_was_active) { - recv_err = rexmpp_tls_recv(s, chunk_raw, 4096, &chunk_raw_len); + 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 = rexmpp_sasl_decode(s, chunk_raw, chunk_raw_len, &chunk, &chunk_len); @@ -1372,13 +1328,13 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) { 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; - xmlNodePtr elem; + 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 @@ -1393,7 +1349,7 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) { err = rexmpp_process_element(s, elem); } } - xmlFreeNodeList(s->input_queue); + rexmpp_xml_free_list(s->input_queue); s->input_queue = NULL; s->input_queue_last = NULL; if (err != REXMPP_SUCCESS && err != REXMPP_E_AGAIN) { @@ -1402,9 +1358,9 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) { } else if (chunk_raw_len == 0) { 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); if (s->stream_state == REXMPP_STREAM_READY || s->stream_state == REXMPP_STREAM_ERROR_RECONNECT) { @@ -1537,7 +1493,7 @@ rexmpp_process_tls_conn_err (rexmpp_t *s, return rexmpp_stream_open(s); } else { /* A STARTTLS connection, restart the stream. */ - xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8"); + s->xml_parser = rexmpp_xml_parser_reset(s->xml_parser); return rexmpp_stream_open(s); } } else { @@ -1552,7 +1508,7 @@ rexmpp_err_t rexmpp_connected_to_server (rexmpp_t *s) { "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_process_tls_conn_err(s, rexmpp_tls_connect(s)); } else { @@ -1631,10 +1587,10 @@ void rexmpp_srv_cb (rexmpp_t *s, 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_E_AGAIN) { return ret; @@ -1651,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, @@ -1665,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 { @@ -1685,8 +1640,8 @@ void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) { void rexmpp_carbons_enabled (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -1703,8 +1658,8 @@ void rexmpp_carbons_enabled (rexmpp_t *s, void rexmpp_pong (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -1716,15 +1671,15 @@ void rexmpp_pong (rexmpp_t *s, void rexmpp_disco_carbons_cb (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; (void)req; (void)response; if (success) { - xmlNodePtr carbons_enable = - rexmpp_xml_new_node("enable", "urn:xmpp:carbons:2"); + 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); @@ -1747,27 +1702,31 @@ void rexmpp_stream_is_ready(rexmpp_t *s) { 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, NULL); } - xmlNodePtr presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); + 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); } @@ -1775,8 +1734,8 @@ void rexmpp_stream_is_ready(rexmpp_t *s) { https://tools.ietf.org/html/rfc6120#section-7 */ void rexmpp_bound (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -1787,24 +1746,22 @@ void rexmpp_bound (rexmpp_t *s, 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")) { - char *jid_str = 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)); - free(jid_str); } 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; @@ -1819,12 +1776,12 @@ void rexmpp_bound (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_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); } -rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { +rexmpp_err_t rexmpp_process_element (rexmpp_t *s, rexmpp_xml_t *elem) { rexmpp_console_on_recv(s, elem); /* Stream negotiation, @@ -1834,22 +1791,23 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { /* Remember features. */ if (s->stream_features != NULL) { - xmlFreeNode(s->stream_features); + rexmpp_xml_free(s->stream_features); } - s->stream_features = xmlCopyNode(elem, 1); + 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. */ - xmlNodePtr starttls = + rexmpp_xml_t *starttls = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls", "starttls"); - xmlNodePtr sasl = + rexmpp_xml_t *sasl = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "mechanisms"); - xmlNodePtr bind = + rexmpp_xml_t *bind = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind"); - xmlNodePtr sm = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm"); + 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 @@ -1857,8 +1815,8 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { if (! (s->tls_policy == REXMPP_TLS_AVOID && (sasl != NULL || bind != NULL || sm != 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_xml_t *starttls_cmd = + rexmpp_xml_new_elem("starttls", "urn:ietf:params:xml:ns:xmpp-tls"); rexmpp_send(s, starttls_cmd); return REXMPP_SUCCESS; } @@ -1872,7 +1830,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } /* Nothing to negotiate. */ - if (xmlFirstElementChild(elem) == NULL) { + if (rexmpp_xml_first_elem_child(elem) == NULL) { rexmpp_stream_is_ready(s); return REXMPP_SUCCESS; } @@ -1882,18 +1840,17 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { s->sasl_state = REXMPP_SASL_NEGOTIATION; char mech_list[2048]; /* todo: perhaps grow it dynamically */ mech_list[0] = '\0'; - xmlNodePtr mechanism; - for (mechanism = xmlFirstElementChild(sasl); + rexmpp_xml_t *mechanism; + for (mechanism = rexmpp_xml_first_elem_child(sasl); mechanism != NULL; - mechanism = xmlNextElementSibling(mechanism)) { + mechanism = rexmpp_xml_next_elem_sibling(mechanism)) { if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl", "mechanism")) { - char *mech_str = xmlNodeGetContent(mechanism); + const char *mech_str = rexmpp_xml_text_child(mechanism); snprintf(mech_list + strlen(mech_list), 2048 - strlen(mech_list), "%s ", mech_str); - free(mech_str); } } const char *mech = rexmpp_sasl_suggest_mechanism(s, mech_list); @@ -1912,10 +1869,10 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { s->sasl_state = REXMPP_SASL_ERROR; return REXMPP_E_SASL; } - 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); + 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; @@ -1925,10 +1882,10 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { 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_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; } @@ -1937,7 +1894,8 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { return rexmpp_stream_bind(s); } } else { - rexmpp_log(s, LOG_ERR, "Expected stream features, received %s", elem->name); + rexmpp_log(s, LOG_ERR, "Expected stream features, received %s", + elem->alt.elem.qname.name); return REXMPP_E_STREAM; } } @@ -1947,18 +1905,18 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { 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"); + 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"); + 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 = (strcmp(id, req_id) == 0); + int id_matches = (req_id != NULL) && (strcmp(id, req_id) == 0); int jid_matches = 0; if (rep_from == NULL) { jid_matches = 1; @@ -1980,29 +1938,20 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { /* 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); prev_req = req; req = req_next; } - free(id); } else if (! rexmpp_jingle_iq(s, elem)) { if (strcmp(type, "set") == 0) { - xmlNodePtr query = xmlFirstElementChild(elem); + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem); int from_server = 0; - char *from = xmlGetProp(elem, "from"); + 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 && @@ -2011,8 +1960,12 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { if (s->roster_ver != NULL) { free(s->roster_ver); } - s->roster_ver = xmlGetProp(query, "ver"); - rexmpp_modify_roster(s, xmlFirstElementChild(query)); + 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) { @@ -2024,9 +1977,9 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { rexmpp_xml_error("cancel", "service-unavailable")); } } else if (strcmp(type, "get") == 0) { - xmlNodePtr query = xmlFirstElementChild(elem); + rexmpp_xml_t *query = rexmpp_xml_first_elem_child(elem); if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info", "query")) { - char *node = xmlGetProp(query, "node"); + 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 && @@ -2035,12 +1988,13 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { 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); + rexmpp_xml_t *result = + rexmpp_xml_new_elem("query", "http://jabber.org/protocol/disco#info"); if (node != NULL) { - xmlNewProp(result, "node", node); + rexmpp_xml_add_attr(result, "node", node); } - xmlAddChild(result, xmlCopyNodeList(rexmpp_disco_info(s))); + 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, @@ -2051,20 +2005,17 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { if (caps_hash != NULL) { free(caps_hash); } - if (node != NULL) { - free(node); - } } 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")) { - xmlNodePtr reply = xmlNewNode(NULL, "query"); - xmlNewNs(reply, "jabber:iq:version", NULL); - xmlNodePtr name = xmlNewNode(NULL, "name"); - xmlNodeAddContent(name, s->client_name); - xmlAddChild(reply, name); - xmlNodePtr version = xmlNewNode(NULL, "version"); - xmlNodeAddContent(version, s->client_version); - xmlAddChild(reply, version); + 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. */ @@ -2073,49 +2024,44 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } } } - free(type); } /* Incoming presence information. */ if (rexmpp_xml_match(elem, "jabber:client", "presence") && s->manage_roster && s->track_roster_presence) { - char *from = xmlGetProp(elem, "from"); + const char *from = rexmpp_xml_find_attr_val(elem, "from"); if (from != NULL) { struct rexmpp_jid from_jid; rexmpp_jid_parse(from, &from_jid); - xmlFree(from); if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL) { /* The bare JID is in the roster. */ - char *type = xmlGetProp(elem, "type"); - xmlNodePtr cur, prev; + 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 = xmlNextElementSibling(cur)) { - char *cur_from = xmlGetProp(cur, "from"); + 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; } - xmlFreeNode(cur); - cur = NULL; + rexmpp_xml_free(cur); + break; } - free(cur_from); } } if (type == NULL) { /* An "available" presence: add it. */ - xmlNodePtr presence = xmlCopyNode(elem, 1); + rexmpp_xml_t *presence = rexmpp_xml_clone(elem); presence->next = s->roster_presence; s->roster_presence = presence; - } else { - free(type); } } } @@ -2123,28 +2069,27 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { /* Incoming messages. */ if (rexmpp_xml_match(elem, "jabber:client", "message")) { - char *from = xmlGetProp(elem, "from"); + const char *from = rexmpp_xml_find_attr_val(elem, "from"); if (from != NULL) { struct rexmpp_jid from_jid; rexmpp_jid_parse(from, &from_jid); - xmlFree(from); if (rexmpp_roster_find_item(s, from_jid.bare, NULL) != NULL || strcmp(from_jid.bare, s->assigned_jid.bare) == 0) { - xmlNodePtr event = + rexmpp_xml_t *event = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/pubsub#event", "event"); if (event != NULL && s->manage_roster && s->track_roster_events) { - xmlNodePtr items = + rexmpp_xml_t *items = rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event", "items"); if (items != NULL) { - char *node = xmlGetProp(items, "node"); + 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. */ - xmlNodePtr prev, cur; + rexmpp_xml_t *prev, *cur; cur = rexmpp_find_event(s, from_jid.bare, node, &prev); if (cur) { if (prev == NULL) { @@ -2152,12 +2097,12 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } else { prev->next = cur->next; } - xmlFreeNode(cur); + rexmpp_xml_free(cur); cur = NULL; } /* Add the new message. */ - xmlNodePtr message = xmlCopyNode(elem, 1); + rexmpp_xml_t *message = rexmpp_xml_clone(elem); message->next = s->roster_events; s->roster_events = message; @@ -2169,56 +2114,50 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { if (s->autojoin_bookmarked_mucs && strcmp(node, "urn:xmpp:bookmarks:1") == 0 && strcmp(from_jid.bare, s->assigned_jid.bare) == 0) { - xmlNodePtr item; - for (item = xmlFirstElementChild(items); + rexmpp_xml_t *item; + for (item = rexmpp_xml_first_elem_child(items); item != NULL; - item = xmlNextElementSibling(item)) { - xmlNodePtr conference = + 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; } - char *item_id = xmlGetProp(item, "id"); + const char *item_id = rexmpp_xml_find_attr_val(item, "id"); if (item_id == NULL) { continue; } - char *autojoin = xmlGetProp(conference, "autojoin"); + const char *autojoin = + rexmpp_xml_find_attr_val(conference, "autojoin"); if (autojoin == NULL) { - free(item_id); continue; } if (strcmp(autojoin, "true") == 0 || strcmp(autojoin, "1") == 0) { - xmlNodePtr presence = - rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "from", s->assigned_jid.full); - xmlNodePtr nick = + rexmpp_xml_t *nick = rexmpp_xml_find_child(conference, "urn:xmpp:bookmarks:1", "nick"); - char *nick_str; + const char *nick_str = NULL; if (nick != NULL) { - nick_str = xmlNodeGetContent(nick); - } else { - nick_str = strdup(s->initial_jid.local); + nick_str = rexmpp_xml_text_child(nick); + } + if (nick_str == NULL) { + nick_str = s->initial_jid.local; } - char *jid = malloc(strlen(item_id) + strlen(nick_str) + 2); - sprintf(jid, "%s/%s", item_id, nick_str); - free(nick_str); - xmlNewProp(presence, "to", jid); - free(jid); - xmlNodePtr x = xmlNewNode(NULL, "x"); - xmlNewNs(x, "http://jabber.org/protocol/muc", NULL); - xmlAddChild(presence, x); - rexmpp_send(s, presence); + 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); } - free(item_id); - free(autojoin); } } - free(node); } } } @@ -2263,23 +2202,21 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { int sasl_err; if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "challenge")) { - char *challenge = xmlNodeGetContent(elem); + const char *challenge = rexmpp_xml_text_child(elem); sasl_err = rexmpp_sasl_step64 (s, challenge, (char**)&sasl_buf); - free(challenge); 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); - return rexmpp_send(s, response); + rexmpp_send(s, response); } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl", "success")) { - char *success = xmlNodeGetContent(elem); + const char *success = rexmpp_xml_text_child(elem); sasl_err = rexmpp_sasl_step64 (s, success, (char**)&sasl_buf); - free(success); free(sasl_buf); if (! sasl_err) { rexmpp_log(s, LOG_DEBUG, "SASL success"); @@ -2288,7 +2225,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { return REXMPP_E_SASL; } s->sasl_state = REXMPP_SASL_ACTIVE; - xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8"); + 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")) { @@ -2302,20 +2239,23 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { 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) { @@ -2327,8 +2267,8 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } } 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); @@ -2349,7 +2289,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { 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"); @@ -2371,69 +2311,69 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) { } -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) { - /* Not checking namespaces beyond URI. */ - (void)nb_namespaces; - (void)namespaces; - (void)nb_defaulted; - - 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) { - (void)prefix; /* Not interested in prefix here. */ 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) { rexmpp_sasl_ctx_cleanup(s); @@ -2453,8 +2393,16 @@ 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 { + /* 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; @@ -2474,12 +2422,16 @@ rexmpp_err_t rexmpp_close (rexmpp_t *s) { } rexmpp_err_t rexmpp_stop (rexmpp_t *s) { - s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED; if (s->stream_state == REXMPP_STREAM_READY) { - xmlNodePtr presence = rexmpp_xml_add_id(s, 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_E_AGAIN) { @@ -2494,8 +2446,8 @@ 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; - if (gettimeofday(&now, NULL) != 0) { + 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; } @@ -2636,16 +2588,52 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } } + /* 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_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); @@ -2694,12 +2682,13 @@ 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) { - rexmpp_tls_err_t err = rexmpp_tls_disconnect(s); + 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 { + } else if (err != REXMPP_TLS_E_AGAIN) { s->tls_state = REXMPP_TLS_ERROR; return REXMPP_E_TLS; } @@ -2771,11 +2760,11 @@ 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) { @@ -2786,36 +2775,55 @@ struct timeval *rexmpp_timeout (rexmpp_t *s, ret = rexmpp_jingle_timeout(s, ret, tv); - struct timeval now; - gettimeofday(&now, NULL); + 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; + 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) * 1000 < ret->tv_usec))) { + (curl_timeout % 1000) * 1000000 < ret->tv_nsec))) { tv->tv_sec = curl_timeout / 1000; - tv->tv_usec = (curl_timeout % 1000) * 1000; + tv->tv_nsec = (curl_timeout % 1000) * 1000000; ret = tv; } #endif diff --git a/src/rexmpp.h b/src/rexmpp.h index c6d4428..0aa6252 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -10,10 +10,10 @@ #define REXMPP_H #include <stdint.h> +#include <stdbool.h> #include "config.h" -#include <libxml/tree.h> #ifdef HAVE_GPGME #include <gpgme.h> #endif @@ -187,6 +187,8 @@ enum tls_pol { typedef enum rexmpp_err rexmpp_err_t; +#include "rexmpp_xml.h" +#include "rexmpp_xml_parser.h" #include "rexmpp_tcp.h" #include "rexmpp_socks.h" #include "rexmpp_dns.h" @@ -207,8 +209,8 @@ typedef enum rexmpp_err rexmpp_err_t; */ typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s, void *cb_data, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success); typedef struct rexmpp_iq rexmpp_iq_t; @@ -217,7 +219,7 @@ typedef struct rexmpp_iq rexmpp_iq_t; struct rexmpp_iq { /** @brief The sent request. */ - xmlNodePtr request; + rexmpp_xml_t *request; /** @brief A callback to call on reply. */ rexmpp_iq_callback_t cb; /** @brief User-supplied data, to pass to a callback function. */ @@ -226,12 +228,31 @@ struct rexmpp_iq rexmpp_iq_t *next; }; +typedef struct rexmpp_muc_ping rexmpp_muc_ping_t; + +/** @brief MUC self-ping data. */ +struct rexmpp_muc_ping +{ + /** @brief Own occupant JID to ping. */ + char *jid; + /** @brief Optional password to rejoin with. */ + char *password; + /** @brief Ping delay, in seconds. */ + unsigned int delay; + /** @brief Whether a ping is requested (pending) already. */ + int requested; + /** @brief When the MUC was active. */ + struct timespec last_activity; + rexmpp_muc_ping_t *next; +}; + typedef void (*log_function_t) (rexmpp_t *s, int priority, const char *format, va_list args); typedef int (*sasl_property_cb_t) (rexmpp_t *s, rexmpp_sasl_property prop); -typedef int (*xml_in_cb_t) (rexmpp_t *s, xmlNodePtr node); -typedef int (*xml_out_cb_t) (rexmpp_t *s, xmlNodePtr node); -typedef void (*roster_modify_cb_t) (rexmpp_t *s, xmlNodePtr item); +typedef int (*xml_in_cb_t) (rexmpp_t *s, rexmpp_xml_t *node); +typedef int (*xml_out_cb_t) (rexmpp_t *s, rexmpp_xml_t *node); +typedef void (*roster_modify_cb_t) (rexmpp_t *s, rexmpp_xml_t *item); typedef int (*console_print_cb_t) (rexmpp_t *s, const char *format, va_list args); +typedef void (*socket_cb_t) (rexmpp_t *s, int socket); /** @brief Complete connection state */ struct rexmpp @@ -253,7 +274,7 @@ struct rexmpp /* Manual host/port configuration. */ const char *manual_host; uint16_t manual_port; - int manual_direct_tls; + bool manual_direct_tls; /* Miscellaneous settings */ const char *disco_node; @@ -263,21 +284,23 @@ struct rexmpp uint16_t socks_port; /* Various knobs (these are used instead of loadable modules). */ - int enable_carbons; /* XEP-0280 */ - int manage_roster; + bool enable_carbons; /* XEP-0280 */ + bool manage_roster; const char *roster_cache_file; - int track_roster_presence; - int track_roster_events; /* XEP-0163 */ - int nick_notifications; /* XEP-0172 */ - int retrieve_openpgp_keys; /* XEP-0373 */ - int autojoin_bookmarked_mucs; /* XEP-0402 */ + bool track_roster_presence; + bool track_roster_events; /* XEP-0163 */ + bool nick_notifications; /* XEP-0172 */ + bool retrieve_openpgp_keys; /* XEP-0373 */ + bool autojoin_bookmarked_mucs; /* XEP-0402 */ enum tls_pol tls_policy; - int enable_jingle; + bool enable_jingle; const char *client_name; /* XEP-0030, XEP-0092 */ const char *client_type; /* XEP-0030 */ const char *client_version; /* XEP-0092 */ const char *local_address; /* For ICE, XEP-0176 */ - int jingle_prefer_rtcp_mux; + bool jingle_prefer_rtcp_mux; + /* A delay in seconds, to use for MUC self-ping by default */ + unsigned int muc_ping_default_delay; /* Resource limits. */ uint32_t stanza_queue_size; @@ -286,6 +309,12 @@ struct rexmpp uint32_t iq_cache_size; uint32_t max_jingle_sessions; + /* X.509 settings: for TLS and DTLS, to use for SASL EXTERNAL + authentication and DTLS-SRTP on Jingle calls. */ + const char *x509_key_file; + const char *x509_cert_file; + const char *x509_trust_file; + /* Callbacks. */ log_function_t log_function; sasl_property_cb_t sasl_property_cb; @@ -293,44 +322,48 @@ struct rexmpp xml_out_cb_t xml_out_cb; roster_modify_cb_t roster_modify_cb; console_print_cb_t console_print_cb; + socket_cb_t socket_cb; /* Stream-related state. */ struct rexmpp_jid assigned_jid; - xmlNodePtr stream_features; - xmlNodePtr roster_items; + rexmpp_xml_t *stream_features; + rexmpp_xml_t *roster_items; char *roster_ver; - xmlNodePtr roster_presence; - xmlNodePtr roster_events; + rexmpp_xml_t *roster_presence; + rexmpp_xml_t *roster_events; /* Other dynamic data. */ - xmlNodePtr disco_info; + rexmpp_xml_t *disco_info; /* Includes Jingle RTP session candidates; rexmpp prioritizes the ones listed earlier on incoming calls. */ - xmlNodePtr jingle_rtp_description; + rexmpp_xml_t *jingle_rtp_description; /* IQs we're waiting for responses to. */ rexmpp_iq_t *active_iq; /* Cached IQ requests and responses. */ - xmlNodePtr iq_cache; + rexmpp_xml_t *iq_cache; /* Jingle context. */ - rexmpp_jingle_ctx_t jingle; + rexmpp_jingle_ctx_t *jingle; /* Connection and stream management. */ unsigned int reconnect_number; time_t reconnect_seconds; - struct timeval next_reconnect_time; - xmlNodePtr stanza_queue; + struct timespec next_reconnect_time; + rexmpp_xml_t *stanza_queue; uint32_t stanzas_out_count; uint32_t stanzas_out_acknowledged; uint32_t stanzas_in_count; char *stream_id; /* Server ping configuration and state. */ - int ping_delay; - int ping_requested; - time_t last_network_activity; + unsigned int ping_delay; + bool ping_requested; + struct timespec last_network_activity; + + /* MUC self-ping */ + rexmpp_muc_ping_t *muc_ping; /* DNS-related structures. */ rexmpp_dns_ctx_t resolver; @@ -348,7 +381,7 @@ struct rexmpp int server_socket; /* Whether the address it's connected to was verified with DNSSEC. */ - int server_socket_dns_secure; + bool server_socket_dns_secure; /* A structure used to establish a TCP connection. */ rexmpp_tcp_conn_t server_connection; @@ -359,34 +392,42 @@ struct rexmpp NULL if there is anything in the send queue). Not appending data to it, see send_queue for queuing. */ char *send_buffer; - ssize_t send_buffer_len; - ssize_t send_buffer_sent; + size_t send_buffer_len; + size_t send_buffer_sent; /* A queue of XML elements to send. */ - xmlNodePtr send_queue; + rexmpp_xml_t *send_queue; + + /* An input queue of parsed XML structures. */ + rexmpp_xml_t *input_queue; + rexmpp_xml_t *input_queue_last; /* XML parser context, and current element pointer for building XML nodes with a SAX2 parser interface. */ - xmlParserCtxtPtr xml_parser; - xmlNodePtr current_element_root; - xmlNodePtr current_element; - xmlNodePtr input_queue; - xmlNodePtr input_queue_last; + rexmpp_xml_parser_ctx_t xml_parser; + + /* The children are stored in reverse order during building. */ + rexmpp_xml_t *current_element_root; + rexmpp_xml_t *current_element; /* TLS structures. */ - rexmpp_tls_t tls; + rexmpp_tls_t *tls; /* SASL structures. */ - rexmpp_sasl_ctx_t sasl; + rexmpp_sasl_ctx_t *sasl; /* OpenPGP structures */ #ifdef HAVE_GPGME gpgme_ctx_t pgp_ctx; +#else + void *pgp_ctx; #endif /* curl structures */ #ifdef HAVE_CURL CURLM *curl_multi; +#else + void *curl_multi; #endif }; @@ -429,7 +470,7 @@ rexmpp_err_t rexmpp_stop (rexmpp_t *s); @param[in] node An XML element to send. The library assumes ownership of the element, so it must not be freed by the caller. */ -rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node); +rexmpp_err_t rexmpp_send (rexmpp_t *s, rexmpp_xml_t *node); /** @brief Prepare and send a new info/query request. @@ -448,7 +489,7 @@ rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node); rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, const char *type, const char *to, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t cb, void *cb_data); @@ -460,7 +501,7 @@ rexmpp_err_t rexmpp_iq_new (rexmpp_t *s, rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, const char *type, const char *to, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t cb, void *cb_data, int fresh); @@ -469,9 +510,9 @@ rexmpp_err_t rexmpp_cached_iq_new (rexmpp_t *s, @brief Reply to an IQ. */ void rexmpp_iq_reply (rexmpp_t *s, - xmlNodePtr req, + rexmpp_xml_t *req, const char *type, - xmlNodePtr payload); + rexmpp_xml_t *payload); /** @brief Determines the maximum time to wait before the next @@ -479,13 +520,13 @@ void rexmpp_iq_reply (rexmpp_t *s, @param[in] s ::rexmpp @param[in] max_tv An existing timeout (can be NULL), to return if there's no more urgent timeouts. - @param[in,out] tv An allocated timeval structure, to store the time - in. + @param[in,out] tv An allocated timespec structure, to store the + time in. @returns A pointer to either max_tv or tv. */ -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); /** @brief Sets file descriptors to watch. @@ -500,35 +541,6 @@ struct timeval *rexmpp_timeout (rexmpp_t *s, int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds); /** - @brief Compose an 'error' element. -*/ -xmlNodePtr rexmpp_xml_error (const char *type, const char *condition); - -/** - @brief A helper function for XML parsing. - @param[in] str A string to parse. - @param[in] str_len String length. - @returns Parsed XML, or NULL on failure. -*/ -xmlNodePtr rexmpp_xml_parse (const char *str, int str_len); - -/** - @brief A helper function for XML serialisation. - @param[in] node An XML node. - @returns A string (must be freed by the caller). -*/ -char *rexmpp_xml_serialize (xmlNodePtr node); - -/** - @brief Adds an "id" attribute to an XML stanza. - @param[in,out] s ::rexmpp - @param[in] node A pointer to an XML stanza. - @returns The same pointer as on input, for more convenient - composition. -*/ -xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node); - -/** @brief The logging function. @param[in] s ::rexmpp @param[in] priority A syslog priority. @@ -547,42 +559,6 @@ void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...); char *rexmpp_get_name (rexmpp_t *s, const char *jid_str); /** - @brief Compares two XML elements. -*/ -int rexmpp_xml_eq (xmlNodePtr n1, xmlNodePtr n2); - -/** - @brief Matches an XML node against a namespace and an element name. - @param[in] node An XML node to match. - @param[in] namespace An XML namespace. Can be NULL (matches - anything), and it is assumed that the default namespace is - "jabber:client" (so if it is "jabber:client" and an element doesn't - have a namespace defined, this function counts that as a match). - @param[in] name Element name. Can be NULL (matches anything). - @returns 1 on a successful match, 0 otherwise. -*/ -int rexmpp_xml_match (xmlNodePtr node, - const char *namespace, - const char *name); - -/** - @brief Finds a child element of an XML node, which matches the - given namespace and name. - @param[in] node The node containing child nodes. - @param[in] namespace The namespace to look for. - @param[in] name The element name to look for. - @returns A pointer to the first matching child node, or NULL if no - matching child elements found. -*/ -xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node, - const char *namespace, - const char *name); - -xmlNodePtr rexmpp_xml_new_node (const char *name, const char *namespace); - -char *rexmpp_gen_id (rexmpp_t *s); - -/** @brief Finds a PEP event. @param[in] s ::rexmpp @param[in] from JID. @@ -591,10 +567,10 @@ char *rexmpp_gen_id (rexmpp_t *s); @returns A pointer to the message announcing an event, or NULL on failure. */ -xmlNodePtr rexmpp_find_event (rexmpp_t *s, - const char *from, - const char *node, - xmlNodePtr *prev_event); +rexmpp_xml_t *rexmpp_find_event (rexmpp_t *s, + const char *from, + const char *node, + rexmpp_xml_t **prev_event); void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len); @@ -632,4 +608,43 @@ rexmpp_disco_find_feature (rexmpp_t *s, int fresh, int max_requests); +/** + @brief Add a MUC JID to self-ping + @param[in,out] s ::rexmpp + @param[in] jid Own occupant JID to ping + @param[in] password Optional password to rejoin with + @param[in] delay How often to ping, in seconds +*/ +rexmpp_err_t rexmpp_muc_ping_set (rexmpp_t *s, + const char *occupant_jid, + const char *password, + unsigned int delay); + +/** + @brief Remove a MUC JID to self-ping + @param[in,out] s ::rexmpp + @param[in] jid Own occupant JID +*/ +rexmpp_err_t rexmpp_muc_ping_remove (rexmpp_t *s, + const char *occupant_jid); + +/** + @brief Join a MUC, optionally setting self-ping + @param[in,out] s ::rexmpp + @param[in] occupant_jid Occupant JID + @param[in] password Optional password + @param[in] ping_delay MUC self-ping delay, 0 to not set it +*/ +rexmpp_err_t rexmpp_muc_join (rexmpp_t *s, + const char *occupant_jid, + const char *password, + unsigned int ping_delay); + +/** + @brief Leave a MUC, stop self-pinging it + @param[in,out] s ::rexmpp + @param[in] occupant_jid Occupant JID +*/ +rexmpp_err_t rexmpp_muc_leave (rexmpp_t *s, const char *occupant_jid); + #endif diff --git a/src/rexmpp.rs b/src/rexmpp.rs new file mode 100644 index 0000000..250da00 --- /dev/null +++ b/src/rexmpp.rs @@ -0,0 +1,280 @@ +extern crate libc; +use std::os::raw::{c_char, c_int, c_void, c_uint}; +use libc::{time_t, timespec}; + +use super::{rexmpp_jid, rexmpp_xml, rexmpp_dns, rexmpp_tcp, rexmpp_socks}; + +#[derive(PartialEq)] +#[repr(C)] +pub enum ResolverState { + Ready, + SRV, + SRV2, + Failure +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum TCPState { + None, + Connecting, + SOCKS, + Connected, + Closed, + ConnectionFailure, + Error +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum StreamState { + None, + Opening, + StartTLS, + SASL, + Bind, + SMFull, + SMAcks, + SMResume, + Ready, + CloseRequested, + Closing, + Closed, + Error, + ErrorReconnect +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum TLSState { + Inactive, + AwaitingDirect, + Handshake, + Active, + Closing, + Closed, + Error +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum SASLState { + Inactive, + Negotiation, + Active, + Error +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum SMState { + Inactive, + Negotiation, + Active +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum CarbonsState { + Inactive, + Negotiation, + Active +} + +#[derive(PartialEq)] +#[repr(C)] +pub enum TLSPolicy { + Require, + Prefer, + Avoid +} + +type IQCallback = unsafe extern "C" +fn (s: *mut Rexmpp, cb_data: *mut c_void, + request: *mut rexmpp_xml::RexmppXML, response: *mut rexmpp_xml::RexmppXML, + success: c_int) -> (); + +type SocketCallback = unsafe extern "C" +fn (s: *mut Rexmpp, socket: c_int) -> (); + +#[repr(C)] +pub struct RexmppIQ { + pub requset: *mut rexmpp_xml::RexmppXML, + pub cb: IQCallback, + pub cb_data: *const c_void, + pub next: *mut RexmppIQ +} + +#[repr(C)] +pub struct RexmppMUCPing { + pub jid: *mut c_char, + pub password: *mut c_char, + pub delay: c_uint, + pub requested: bool, + pub last_activity: timespec, + pub next: *mut RexmppMUCPing +} + +#[repr(C)] +pub struct Rexmpp { + pub resolver_state: ResolverState, + pub tcp_state: TCPState, + pub stream_state: StreamState, + pub tls_state: TLSState, + pub sasl_state: SASLState, + pub sm_state: SMState, + pub carbons_state: CarbonsState, + + // Basic configuration + pub initial_jid: rexmpp_jid::RexmppJID, + + // Manual host/port configuration + pub manual_host: *const c_char, + pub manual_port: u16, + pub manual_direct_tls: bool, + + // Miscellaneous settings + pub disco_node: *const c_char, + + // SOCKS settings + pub socks_host: *const c_char, + pub socks_port: u16, + + // Various knobs (these are used instead of loadable modules) + pub enable_carbons: bool, // XEP-0280 + pub manage_roster: bool, + pub roster_cache_file: *const c_char, + pub track_roster_presence: bool, + pub track_roster_events: bool, // XEP-0163 + pub nick_notifications: bool, // XEP-0172 + pub retrieve_openpgp_keys: bool, // XEP-0373 + pub autojoin_bookmarked_mucs: bool, // XEP-0402 + pub tls_policy: TLSPolicy, + pub enable_jingle: bool, + pub client_name: *const c_char, // XEP-0030, XEP-0092 + pub client_type: *const c_char, // XEP-0030 + pub client_version: *const c_char, // XEP-0092 + pub local_address: *const c_char, // For ICE, XEP-0176 + pub jingle_prefer_rtcp_mux: bool, + // A delay in seconds, to use for MUC self-ping by default + pub muc_ping_default_delay: c_uint, + // Resource limits + pub stanza_queue_size: u32, + pub send_queue_size: u32, + pub iq_queue_size: u32, + pub iq_cache_size: u32, + pub max_jingle_sessions: u32, + + // X.509 settings (for TLS and DTLS) + pub x509_key_file: *const c_char, + pub x509_cert_file: *const c_char, + pub x509_trust_file: *const c_char, + + // Callbacks + + // c_variadic is experimental and cannot be used on the stable + // release channel, so skipping the log function callback. + pub log_function: *const c_void, + // Actually skipping proper definitions of others for now as well + // (TODO: add them). + pub sasl_property_cb: *const c_void, + pub xml_in_cb: *const c_void, + pub xml_out_cb: *const c_void, + pub roster_modify_cb: *const c_void, + pub console_print_cb: *const c_void, + pub socket_cb: Option<SocketCallback>, + + // Stream-related state + pub assigned_jid: rexmpp_jid::RexmppJID, + pub stream_features: *mut rexmpp_xml::RexmppXML, + pub roster_items: *mut rexmpp_xml::RexmppXML, + pub roster_ver: *mut c_char, + pub roster_presence: *mut rexmpp_xml::RexmppXML, + pub roster_events: *mut rexmpp_xml::RexmppXML, + + // Other dynamic data + pub disco_info: *mut rexmpp_xml::RexmppXML, + // Includes Jingle RTP session candidates; rexmpp prioritizes the + // ones listed earlier on incoming calls + pub jingle_rtp_description: *mut rexmpp_xml::RexmppXML, + + // IQs we're waiting for responses to + pub active_iq: *mut RexmppIQ, + pub iq_cache: *mut rexmpp_xml::RexmppXML, + + // Jingle context + pub jingle: *const c_void, // TODO + + // Connection and stream management + pub reconnect_number: c_uint, + pub reconnect_seconds: time_t, + pub next_reconnect_time: timespec, + pub stanza_queue: *mut rexmpp_xml::RexmppXML, + pub stanzas_out_count: u32, + pub stanzas_out_acknowledged: u32, + pub stanzas_in_count: u32, + pub stream_id: *mut c_char, + + // Server ping configuration and state + pub ping_delay: c_uint, + pub ping_requested: bool, + pub last_network_activity: timespec, + + // MUC self-ping + pub muc_ping: *mut RexmppMUCPing, + + // DNS-related structures + pub resolver: *mut c_void, + pub server_srv: *mut rexmpp_dns::RexmppDNSResult, + pub server_srv_cur: c_int, + pub server_srv_tls: *mut rexmpp_dns::RexmppDNSResult, + pub server_srv_tls_cur: c_int, + pub server_active_srv: *mut rexmpp_dns::RexmppDNSSRV, + + // The XMPP server we are connecting to + pub server_host: *const c_char, + pub server_port: u16, + + // The primary socket used for communication with the server + pub server_socket: c_int, + // Whether the address it's connected to was verified with DNSSEC + pub server_socket_dns_secure: bool, + + // A structure used to establish a TCP connection + pub server_connection: rexmpp_tcp::RexmppTCPConnection, + pub server_socks_conn: rexmpp_socks::RexmppSocks, + + // Send buffer. NULL if there is nothing to send (and must not be + // NULL if there is anything in the send queue). Not appending + // data to it, see send_queue for queuing. + pub send_buffer: *mut c_char, + pub send_buffer_len: isize, + pub send_buffer_sent: isize, + + // A queue of XML elements to send + pub send_queue: *mut rexmpp_xml::RexmppXML, + + // An input queue of parsed XML structures + pub input_queue: *mut rexmpp_xml::RexmppXML, + pub input_queue_last: *mut rexmpp_xml::RexmppXML, + + // XML parser context, and current element pointer for building + // XML nodes with a SAX2 parser interface + pub xml_parser: *mut c_void, + + // The children are stored in reverse order during building + pub current_element_root: *mut rexmpp_xml::RexmppXML, + pub current_element: *mut rexmpp_xml::RexmppXML, + + // TLS structures + pub tls: *mut c_void, + + // SASL structures + pub sasl: *mut c_void, + + // OpenPGP structures + pub pgp_ctx: *mut c_void, + + // curl structures + pub curl_multi: *mut c_void +} diff --git a/src/rexmpp_base64.h b/src/rexmpp_base64.h index e681ed9..d11cd91 100644 --- a/src/rexmpp_base64.h +++ b/src/rexmpp_base64.h @@ -10,5 +10,26 @@ #include <stddef.h> -int rexmpp_base64_to (const char *in, size_t in_len, char **out, size_t *out_len); -int rexmpp_base64_from (const char *in, size_t in_len, char **out, size_t *out_len); +/** + @brief Encodes data in Base64 + @param[in] in Data to encode + @param[in] in_len Length of the input data + @param[out] out A pointer to the output buffer; its memory will be + allocated by the function, the caller receives ownership over it + @param[out] out_len Length of the produced Base64-encoded string + @returns 0 on success, a non-zero value otherwise +*/ +int rexmpp_base64_to (const char *in, size_t in_len, + char **out, size_t *out_len); + +/** + @brief Decodes data from Base64 + @param[in] in Data to decode + @param[in] in_len Length of the input data + @param[out] out A pointer to the output buffer; its memory will be + allocated by the function, the caller receives ownership over it + @param[out] out_len Length of the decoded string + @returns 0 on success, a non-zero value otherwise +*/ +int rexmpp_base64_from (const char *in, size_t in_len, + char **out, size_t *out_len); diff --git a/src/rexmpp_console.c b/src/rexmpp_console.c index f2d748f..a6f859a 100644 --- a/src/rexmpp_console.c +++ b/src/rexmpp_console.c @@ -11,11 +11,15 @@ */ #include <string.h> +#include <stdlib.h> +#include <stdarg.h> #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_openpgp.h" #include "rexmpp_http_upload.h" #include "rexmpp_jingle.h" +#include "rexmpp_pubsub.h" #include "rexmpp_console.h" @@ -29,61 +33,60 @@ void rexmpp_console_printf (rexmpp_t *s, const char *format, ...) } } -char *rexmpp_console_message_string (rexmpp_t *s, xmlNodePtr node) { - char *ret = NULL; - xmlNodePtr openpgp = +const char *rexmpp_console_message_string (rexmpp_t *s, rexmpp_xml_t *node) { + const char *ret = NULL; + rexmpp_xml_t *openpgp = rexmpp_xml_find_child(node, "urn:xmpp:openpgp:0", "openpgp"); if (openpgp != NULL) { int valid; - xmlNodePtr elem = rexmpp_openpgp_decrypt_verify_message(s, node, &valid); + rexmpp_xml_t *elem = rexmpp_openpgp_decrypt_verify_message(s, node, &valid); if (! valid) { rexmpp_console_printf(s, "An invalid OpenPGP message!\n"); } if (elem != NULL) { - xmlNodePtr payload = + rexmpp_xml_t *payload = rexmpp_xml_find_child(elem, "urn:xmpp:openpgp:0", "payload"); if (payload != NULL) { - xmlNodePtr pl_body = + rexmpp_xml_t *pl_body = rexmpp_xml_find_child(payload, "jabber:client", "body"); if (pl_body != NULL) { - ret = xmlNodeGetContent(pl_body); + ret = rexmpp_xml_text_child(pl_body); } } - xmlFreeNode(elem); + rexmpp_xml_free(elem); } } if (ret == NULL) { - xmlNodePtr body = rexmpp_xml_find_child(node, "jabber:client", "body"); - ret = xmlNodeGetContent(body); + rexmpp_xml_t *body = + rexmpp_xml_find_child(node, "jabber:client", "body"); + ret = rexmpp_xml_text_child(body); } return ret; } -void rexmpp_console_on_send (rexmpp_t *s, xmlNodePtr node) { +void rexmpp_console_on_send (rexmpp_t *s, rexmpp_xml_t *node) { if (rexmpp_xml_match(node, "jabber:client", "message")) { - char *to = xmlGetProp(node, "to"); + const char *to = rexmpp_xml_find_attr_val(node, "to"); if (to != NULL) { /* "from" should be set for verification. */ - char *from = xmlGetProp(node, "from"); - xmlAttrPtr fromProp = NULL; - if (from == NULL) { - fromProp = xmlNewProp(node, "from", to); + int added_from = 0; + if (rexmpp_xml_find_attr_val(node, "from") == NULL) { + rexmpp_xml_add_attr(node, "from", to); + added_from = 1; } - char *str = rexmpp_console_message_string(s, node); - if (fromProp != NULL) { - xmlRemoveProp(fromProp); + const char *str = rexmpp_console_message_string(s, node); + if (added_from) { + rexmpp_xml_remove_attr(node, "from"); } if (str != NULL) { rexmpp_console_printf(s, "You tell %s: %s\n", to, str); - free(str); } - free(to); } } if (rexmpp_xml_match(node, "jabber:client", "presence")) { - char *presence_type = xmlGetProp(node, "type"); - char *presence_to = xmlGetProp(node, "to"); + const char *presence_type = rexmpp_xml_find_attr_val(node, "type"); + const char *presence_to = rexmpp_xml_find_attr_val(node, "to"); if (presence_to == NULL) { rexmpp_console_printf(s, "Becoming %s\n", (presence_type == NULL) ? @@ -105,72 +108,58 @@ void rexmpp_console_on_send (rexmpp_t *s, xmlNodePtr node) { "Denying %s's presence subscription request.\n", presence_to); } - free(presence_to); - } - if (presence_type != NULL) { - free(presence_type); } } } -void rexmpp_console_on_recv (rexmpp_t *s, xmlNodePtr node) { +void rexmpp_console_on_recv (rexmpp_t *s, rexmpp_xml_t *node) { if (rexmpp_xml_match(node, "jabber:client", "message")) { - xmlNodePtr sent = rexmpp_xml_find_child(node, "urn:xmpp:carbons:2", "sent"); + rexmpp_xml_t *sent = rexmpp_xml_find_child(node, "urn:xmpp:carbons:2", "sent"); if (sent != NULL) { - xmlNodePtr fwd = + rexmpp_xml_t *fwd = rexmpp_xml_find_child(sent, "urn:xmpp:forward:0", "forwarded"); if (fwd != NULL) { - xmlNodePtr msg = + rexmpp_xml_t *msg = rexmpp_xml_find_child(fwd, "jabber:client", "message"); if (msg != NULL) { - char *to = xmlGetProp(msg, "to"); - char *str = rexmpp_console_message_string(s, msg); + const char *to = rexmpp_xml_find_attr_val(msg, "to"); + const char *str = rexmpp_console_message_string(s, msg); if (str != NULL) { rexmpp_console_printf(s, "You tell %s: %s\n", to, str); - free(str); - } - if (to != NULL) { - free(to); } } } } - xmlNodePtr received = + rexmpp_xml_t *received = rexmpp_xml_find_child(node, "urn:xmpp:carbons:2", "received"); if (received != NULL) { - xmlNodePtr fwd = + rexmpp_xml_t *fwd = rexmpp_xml_find_child(received, "urn:xmpp:forward:0", "forwarded"); if (fwd != NULL) { - xmlNodePtr msg = + rexmpp_xml_t *msg = rexmpp_xml_find_child(fwd, "jabber:client", "message"); if (msg != NULL) { - char *from = xmlGetProp(msg, "from"); - char *str = rexmpp_console_message_string(s, msg); + const char *from = rexmpp_xml_find_attr_val(msg, "from"); + const char *str = rexmpp_console_message_string(s, msg); if (str != NULL) { rexmpp_console_printf(s, "%s tells you: %s\n", from, str); - free(str); - } - if (from != NULL) { - free(from); } } } } - char *from = xmlGetProp(node, "from"); + const char *from = rexmpp_xml_find_attr_val(node, "from"); if (from != NULL) { - char *str = rexmpp_console_message_string(s, node); + const char *str = rexmpp_console_message_string(s, node); if (str != NULL) { rexmpp_console_printf(s, "%s tells you: %s\n", from, str); - free(str); } - free(from); } } if (rexmpp_xml_match(node, "jabber:client", "presence")) { - char *presence_type = xmlGetProp(node, "type"); - char *from = xmlGetProp(node, "from"); + const char *presence_type = rexmpp_xml_find_attr_val(node, "type"); + const char *from = rexmpp_xml_find_attr_val(node, "from"); if (presence_type != NULL && ! strcmp(presence_type, "subscribe")) { rexmpp_console_printf(s, "%s requests a presence subscription\n", from); } else if (presence_type != NULL && ! strcmp(presence_type, "subscribed")) { @@ -182,74 +171,64 @@ void rexmpp_console_on_recv (rexmpp_t *s, xmlNodePtr node) { (presence_type == NULL) ? "available" : presence_type); - xmlNodePtr show = rexmpp_xml_find_child(node, "jabber:client", "show"); + rexmpp_xml_t *show = + rexmpp_xml_find_child(node, "jabber:client", "show"); if (show != NULL) { - char *show_str = xmlNodeGetContent(show); - rexmpp_console_printf(s, " (%s)", show_str); - free(show_str); - show_str = NULL; + rexmpp_console_printf(s, " (%s)", + rexmpp_xml_text_child(show)); } - xmlNodePtr status = rexmpp_xml_find_child(node, "jabber:client", "status"); + rexmpp_xml_t *status = + rexmpp_xml_find_child(node, "jabber:client", "status"); if (status != NULL) { - char *status_str = xmlNodeGetContent(status); - rexmpp_console_printf(s, ": %s", status_str); - free(status_str); - status_str = NULL; + rexmpp_console_printf(s, ": %s", + rexmpp_xml_text_child(status)); } rexmpp_console_printf(s, "\n"); } - if (presence_type != NULL) { - free(presence_type); - } - if (from != NULL) { - free(from); - } } } void rexmpp_console_roster_deleted (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; (void)response; - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(rexmpp_xml_find_child(req, "jabber:iq:roster", "query"), "jabber:iq:roster", "item"); - char *jid = xmlGetProp(item, "jid"); + const char *jid = rexmpp_xml_find_attr_val(item, "jid"); if (success) { rexmpp_console_printf(s, "Deleted %s from the roster.\n", jid); } else { rexmpp_console_printf(s, "Failed to delete %s from the roster.\n", jid); } - free(jid); } void rexmpp_console_roster_added (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; (void)response; - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(rexmpp_xml_find_child(req, "jabber:iq:roster", "query"), "jabber:iq:roster", "item"); - char *jid = xmlGetProp(item, "jid"); + const char *jid = rexmpp_xml_find_attr_val(item, "jid"); if (success) { rexmpp_console_printf(s, "Added %s into the roster.\n", jid); } else { rexmpp_console_printf(s, "Failed to add %s into the roster.\n", jid); } - free(jid); } void rexmpp_console_on_run (rexmpp_t *s, rexmpp_err_t result) { @@ -269,11 +248,210 @@ void rexmpp_console_on_upload (rexmpp_t *s, void *cb_data, const char *url) { free(fpath); } +void rexmpp_console_disco_info (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + if (! success) { + rexmpp_console_printf(s, "Failed to discover info.\n"); + return; + } + rexmpp_xml_t *query = + rexmpp_xml_find_child(response, "http://jabber.org/protocol/disco#info", + "query"); + if (query == NULL) { + rexmpp_console_printf(s, "No disco#info query in response.\n"); + return; + } + const char *from = rexmpp_xml_find_attr_val(response, "from"); + if (from == NULL) { + rexmpp_console_printf(s, "No 'from' property in response.\n"); + return; + } + rexmpp_console_printf(s, "Discovered info for %s:\n", from); + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query); + while (child != NULL) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", + "feature")) { + const char *var = rexmpp_xml_find_attr_val(child, "var"); + rexmpp_console_printf(s, "- feature var %s\n", var); + } else if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info", + "identity")) { + const char *category = rexmpp_xml_find_attr_val(child, "category"); + const char *type = rexmpp_xml_find_attr_val(child, "type"); + const char *name = rexmpp_xml_find_attr_val(child, "name"); + rexmpp_console_printf(s, "- identity name %s, type %s, category %s\n", + name, type, category); + } else { + rexmpp_console_printf(s, "Encountered an unknown disco#info element.\n"); + } + child = rexmpp_xml_next_elem_sibling(child); + } + rexmpp_console_printf(s, "(end of discovered info for %s)\n", from); +} + +void rexmpp_console_disco_items (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + if (! success) { + rexmpp_console_printf(s, "Failed to discover items.\n"); + return; + } + rexmpp_xml_t *query = + rexmpp_xml_find_child(response, "http://jabber.org/protocol/disco#items", + "query"); + if (query == NULL) { + rexmpp_console_printf(s, "No disco#items query in response.\n"); + return; + } + const char *from = rexmpp_xml_find_attr_val(response, "from"); + if (from == NULL) { + rexmpp_console_printf(s, "No 'from' property in response.\n"); + return; + } + rexmpp_console_printf(s, "Discovered items for %s:\n", from); + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(query); + while (child != NULL) { + if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#items", + "item")) { + const char *jid = rexmpp_xml_find_attr_val(child, "jid"); + const char *name = rexmpp_xml_find_attr_val(child, "name"); + const char *node = rexmpp_xml_find_attr_val(child, "node"); + rexmpp_console_printf(s, "- item jid %s, name %s, node %s\n", jid, name, node); + } else { + rexmpp_console_printf(s, "Encountered an unknown disco#items element.\n"); + } + child = rexmpp_xml_next_elem_sibling(child); + } + rexmpp_console_printf(s, "(end of discovered items for %s)\n", from); +} + +void rexmpp_console_pubsub_node_deleted (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_console_printf(s, "Deleted the pubsub node.\n"); + } else { + rexmpp_console_printf(s, "Failed to delete the pubsub node.\n"); + } +} + +void rexmpp_bookmark_added (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_console_printf(s, "Added a bookmark.\n"); + } else { + rexmpp_console_printf(s, "Failed to add a bookmark.\n"); + } +} + +void rexmpp_bookmark_removed (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_console_printf(s, "Removed a bookmark.\n"); + } else { + rexmpp_console_printf(s, "Failed to remove a bookmark.\n"); + } +} + +void rexmpp_console_blocklist (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + if (success) { + rexmpp_xml_t *bl = + rexmpp_xml_find_child(response, "urn:xmpp:blocking", "blocklist"); + if (bl == NULL) { + rexmpp_console_printf(s, "No blocklist element in the response.\n"); + return; + } + rexmpp_console_printf(s, "Block list:"); + rexmpp_xml_t *child = rexmpp_xml_first_elem_child(bl); + while (child != NULL) { + if (rexmpp_xml_match(child, "urn:xmpp:blocking", "item")) { + const char *jid = rexmpp_xml_find_attr_val(child, "jid"); + rexmpp_console_printf(s, " %s", jid); + } else { + rexmpp_console_printf(s, "Encountered an unknown blocklist child element.\n"); + } + child = rexmpp_xml_next_elem_sibling(child); + } + rexmpp_console_printf(s, "\n"); + } else { + rexmpp_console_printf(s, "Failed to retrieve block list.\n"); + } +} + +void rexmpp_console_blocklist_blocked (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_console_printf(s, "Blocklisted successfully.\n"); + } else { + rexmpp_console_printf(s, "Failed to blocklist.\n"); + } +} + +void rexmpp_console_blocklist_unblocked (rexmpp_t *s, + void *ptr, + rexmpp_xml_t *req, + rexmpp_xml_t *response, + int success) +{ + (void)ptr; + (void)req; + (void)response; + if (success) { + rexmpp_console_printf(s, "Un-blocklisted successfully.\n"); + } else { + rexmpp_console_printf(s, "Failed to un-blocklist.\n"); + } +} + void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { /* todo: buffering */ (void)str_len; /* Unused for now (todo). */ char *words_save_ptr; - xmlNodePtr presence; + rexmpp_xml_t *presence; char *word, *jid_str, *msg_text; struct rexmpp_jid jid; word = strtok_r(str, " ", &words_save_ptr); @@ -294,6 +472,8 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { "muc join <conference> [as] <nick>\n" "muc leave <conference> [as] <nick>\n" "muc tell <conference> <message>\n" + "bookmark add <conference> [autojoin] [nick] [password]\n" + "bookmark remove <conference>\n" "roster list\n" "roster add <jid>\n" "roster delete <jid>\n" @@ -305,8 +485,14 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { "jingle decline <sid>\n" "jingle accept-file <sid> <file path>\n" "jingle send-file <jid> <file path>\n" - "jingle accept-call <sid> <in port> <out port>\n" - "jingle call <jid> <in port> <out port>\n" + "jingle accept-call <sid>\n" + "jingle call <jid>\n" + "disco info <jid>\n" + "disco items <jid>\n" + "pubsub node delete <service_jid> <node>\n" + "blocklist\n" + "blocklist block <jid>\n" + "blocklist unblock <jid>\n" ; if (! strcmp(word, "help")) { @@ -337,11 +523,14 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } msg_text = jid_str + strlen(jid_str) + 1; - xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); - xmlNewNs(msg, "jabber:client", NULL); - xmlNewProp(msg, "to", jid.full); - xmlNewProp(msg, "type", "chat"); - xmlNewTextChild(msg, NULL, "body", msg_text); + + rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client"); + rexmpp_xml_add_id(msg); + rexmpp_xml_add_attr(msg, "to", jid.full); + rexmpp_xml_add_attr(msg, "type", "chat"); + rexmpp_xml_t *body = rexmpp_xml_new_elem("body", NULL); + rexmpp_xml_add_text(body, msg_text); + rexmpp_xml_add_child(msg, body); rexmpp_send(s, msg); } @@ -353,9 +542,9 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } msg_text = jid_str + strlen(jid_str) + 1; - xmlNodePtr body = xmlNewNode(NULL, "body"); - xmlNewNs(body, "jabber:client", NULL); - xmlNodeAddContent(body, msg_text); + rexmpp_xml_t *body = + rexmpp_xml_new_elem("body", "jabber:client"); + rexmpp_xml_add_text(body, msg_text); const char *rcpt[2]; rcpt[0] = jid.full; rcpt[1] = NULL; @@ -367,21 +556,20 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { } else if (strcmp(word, "crypt") == 0) { b64 = rexmpp_openpgp_payload(s, body, rcpt, NULL, REXMPP_OX_CRYPT); } - xmlNodePtr openpgp = xmlNewNode(NULL, "openpgp"); - openpgp->ns = xmlNewNs(openpgp, "urn:xmpp:openpgp:0", NULL); - xmlNodeAddContent(openpgp, b64); + rexmpp_xml_t *openpgp = + rexmpp_xml_new_elem("openpgp", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_text(openpgp, b64); free(b64); - xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); - xmlNewNs(msg, "jabber:client", NULL); - xmlNewProp(msg, "to", jid.full); - xmlNewProp(msg, "type", "chat"); - xmlAddChild(msg, openpgp); + rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client"); + rexmpp_xml_add_id(msg); + rexmpp_xml_add_attr(msg, "to", jid.full); + rexmpp_xml_add_attr(msg, "type", "chat"); + rexmpp_xml_add_child(msg, openpgp); - body = xmlNewNode(NULL, "body"); - xmlNewNs(body, "jabber:client", NULL); - xmlNodeAddContent(body, "This is a secret message."); - xmlAddChild(msg, body); + body = rexmpp_xml_new_elem("body", "jabber:client"); + rexmpp_xml_add_text(body, "This is a secret message."); + rexmpp_xml_add_child(msg, body); rexmpp_send(s, msg); } @@ -394,11 +582,14 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } msg_text = jid_str + strlen(jid_str) + 1; - xmlNodePtr msg = rexmpp_xml_add_id(s, xmlNewNode(NULL, "message")); - xmlNewNs(msg, "jabber:client", NULL); - xmlNewProp(msg, "to", jid.full); - xmlNewProp(msg, "type", "groupchat"); - xmlNewTextChild(msg, NULL, "body", msg_text); + + rexmpp_xml_t *msg = rexmpp_xml_new_elem("message", "jabber:client"); + rexmpp_xml_add_id(msg); + rexmpp_xml_add_attr(msg, "to", jid.full); + rexmpp_xml_add_attr(msg, "type", "groupchat"); + rexmpp_xml_t *body = rexmpp_xml_new_elem("body", NULL); + rexmpp_xml_add_text(body, msg_text); + rexmpp_xml_add_child(msg, body); rexmpp_send(s, msg); } if (! strcmp(word, "join")) { @@ -407,23 +598,20 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + return; + } if (! strcmp(word, "as")) { word = strtok_r(NULL, " ", &words_save_ptr); } if (word == NULL) { return; } - char *full_jid = malloc(strlen(jid.bare) + strlen(word) + 2); - snprintf(full_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s", + char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2); + snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s", jid.bare, word); - presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "from", s->assigned_jid.full); - xmlNewProp(presence, "to", full_jid); - xmlNodePtr x = xmlNewNode(NULL, "x"); - xmlNewNs(x, "http://jabber.org/protocol/muc", NULL); - xmlAddChild(presence, x); - rexmpp_send(s, presence); - free(full_jid); + rexmpp_muc_join(s, occupant_jid, NULL, s->muc_ping_default_delay); + free(occupant_jid); } if (! strcmp(word, "leave")) { jid_str = strtok_r(NULL, " ", &words_save_ptr); @@ -431,21 +619,58 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + return; + } if (! strcmp(word, "as")) { word = strtok_r(NULL, " ", &words_save_ptr); } if (word == NULL) { return; } - char *full_jid = malloc(strlen(jid.bare) + strlen(word) + 2); - snprintf(full_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s", + char *occupant_jid = malloc(strlen(jid.bare) + strlen(word) + 2); + snprintf(occupant_jid, strlen(jid_str) + strlen(word) + 2, "%s/%s", jid.bare, word); - presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "from", s->assigned_jid.full); - xmlNewProp(presence, "to", full_jid); - xmlNewProp(presence, "type", "unavailable"); - rexmpp_send(s, presence); - free(full_jid); + rexmpp_muc_leave(s, occupant_jid); + free(occupant_jid); + } + } + + if (! strcmp(word, "bookmark")) { + word = strtok_r(NULL, " ", &words_save_ptr); + if (! strcmp(word, "add")) { + char *muc_jid = strtok_r(NULL, " ", &words_save_ptr); + if (muc_jid == NULL) { + return; + } + char *autojoin = strtok_r(NULL, " ", &words_save_ptr); + char *nick_str = strtok_r(NULL, " ", &words_save_ptr); + char *password = strtok_r(NULL, " ", &words_save_ptr); + + rexmpp_xml_t *conference = + rexmpp_xml_new_elem("conference", "urn:xmpp:bookmarks:1"); + if (autojoin != NULL) { + rexmpp_xml_add_attr(conference, "autojoin", autojoin); + } + if (password != NULL) { + rexmpp_xml_add_attr(conference, "password", password); + } + if (nick_str != NULL) { + rexmpp_xml_t *nick = + rexmpp_xml_new_elem("nick", "urn:xmpp:bookmarks:1"); + rexmpp_xml_add_text(nick, nick_str); + rexmpp_xml_add_child(conference, nick); + } + rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:bookmarks:1", muc_jid, + conference, rexmpp_bookmark_added, NULL); + } + if (! strcmp(word, "remove")) { + char *muc_jid = strtok_r(NULL, " ", &words_save_ptr); + if (muc_jid == NULL) { + return; + } + rexmpp_pubsub_item_retract(s, NULL, "urn:xmpp:bookmarks:1", muc_jid, + rexmpp_bookmark_removed, NULL); } } @@ -455,25 +680,26 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { return; } if (! strcmp(word, "list")) { - xmlNodePtr item; + rexmpp_xml_t *item; for (item = s->roster_items; item != NULL; - item = xmlNextElementSibling(item)) { - char *item_jid = xmlGetProp(item, "jid"); - char *item_ask = xmlGetProp(item, "ask"); - char *item_subscription = xmlGetProp(item, "subscription"); + item = item->next) { + const char *item_jid = rexmpp_xml_find_attr_val(item, "jid"); + const char *item_ask = rexmpp_xml_find_attr_val(item, "ask"); + const char *item_subscription = + rexmpp_xml_find_attr_val(item, "subscription"); char *item_presence = "unavailable"; if (s->track_roster_presence) { for (presence = s->roster_presence; presence != NULL; - presence = xmlNextElementSibling(presence)) { - char *presence_from = xmlGetProp(presence, "from"); + presence = presence->next) { + const char *presence_from = + rexmpp_xml_find_attr_val(presence, "from"); if (presence_from != NULL) { rexmpp_jid_parse(presence_from, &jid); if (! strcmp(jid.bare, item_jid)) { item_presence = "available"; } - free(presence_from); } } } @@ -482,28 +708,19 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { "presence = %s\n", item_jid, item_subscription, item_ask, item_presence); - if (item_jid != NULL) { - free(item_jid); - } - if (item_ask != NULL) { - free(item_ask); - } - if (item_subscription != NULL) { - free(item_subscription); - } } } else if (! strcmp(word, "delete")) { word = strtok_r(NULL, " ", &words_save_ptr); if (word == NULL) { return; } - xmlNodePtr delete_item = xmlNewNode(NULL, "item"); - delete_item->ns = xmlNewNs(delete_item, "jabber:iq:roster", NULL); - xmlNewProp(delete_item, "jid", word); - xmlNewProp(delete_item, "subscription", "remove"); - xmlNodePtr delete_query = xmlNewNode(NULL, "query"); - delete_query->ns = xmlNewNs(delete_query, "jabber:iq:roster", NULL); - xmlAddChild(delete_query, delete_item); + rexmpp_xml_t *delete_item = + rexmpp_xml_new_elem("item", "jabber:iq:roster"); + rexmpp_xml_add_attr(delete_item, "jid", word); + rexmpp_xml_add_attr(delete_item, "subscription", "remove"); + rexmpp_xml_t *delete_query = + rexmpp_xml_new_elem("query", "jabber:iq:roster"); + rexmpp_xml_add_child(delete_query, delete_item); rexmpp_iq_new(s, "set", NULL, delete_query, rexmpp_console_roster_deleted, NULL); } else if (! strcmp(word, "add")) { @@ -511,13 +728,13 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { if (word == NULL) { return; } - xmlNodePtr delete_item = xmlNewNode(NULL, "item"); - delete_item->ns = xmlNewNs(delete_item, "jabber:iq:roster", NULL); - xmlNewProp(delete_item, "jid", word); - xmlNodePtr delete_query = xmlNewNode(NULL, "query"); - delete_query->ns = xmlNewNs(delete_query, "jabber:iq:roster", NULL); - xmlAddChild(delete_query, delete_item); - rexmpp_iq_new(s, "set", NULL, delete_query, + rexmpp_xml_t *add_item = + rexmpp_xml_new_elem("item", "jabber:iq:roster"); + rexmpp_xml_add_attr(add_item, "jid", word); + rexmpp_xml_t *add_query = + rexmpp_xml_new_elem("query", "jabber:iq:roster"); + rexmpp_xml_add_child(add_query, add_item); + rexmpp_iq_new(s, "set", NULL, add_query, rexmpp_console_roster_added, NULL); } } @@ -532,9 +749,10 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { if (word == NULL) { return; } - presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "to", word); - xmlNewProp(presence, "type", "subscribe"); + presence = rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "to", word); + rexmpp_xml_add_attr(presence, "type", "subscribe"); rexmpp_send(s, presence); } if (! strcmp(word, "approve")) { @@ -542,9 +760,10 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { if (word == NULL) { return; } - presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "to", word); - xmlNewProp(presence, "type", "subscribed"); + presence = rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "to", word); + rexmpp_xml_add_attr(presence, "type", "subscribed"); rexmpp_send(s, presence); } if (! strcmp(word, "deny")) { @@ -552,9 +771,10 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { if (word == NULL) { return; } - presence = rexmpp_xml_add_id(s, xmlNewNode(NULL, "presence")); - xmlNewProp(presence, "to", word); - xmlNewProp(presence, "type", "unsubscribed"); + presence = rexmpp_xml_new_elem("presence", "jabber:client"); + rexmpp_xml_add_id(presence); + rexmpp_xml_add_attr(presence, "to", word); + rexmpp_xml_add_attr(presence, "type", "unsubscribed"); rexmpp_send(s, presence); } } @@ -574,7 +794,7 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { char *sid = strtok_r(NULL, " ", &words_save_ptr); if (sid != NULL) { rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("success", + rexmpp_xml_new_elem("success", "urn:xmpp:jingle:1"), NULL); } @@ -582,7 +802,7 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { char *sid = strtok_r(NULL, " ", &words_save_ptr); if (sid != NULL) { rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("decline", + rexmpp_xml_new_elem("decline", "urn:xmpp:jingle:1"), NULL); } @@ -600,18 +820,95 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { } } else if (! strcmp(word, "accept-call")) { char *sid = strtok_r(NULL, " ", &words_save_ptr); - char *port_in = strtok_r(NULL, " ", &words_save_ptr); - char *port_out = strtok_r(NULL, " ", &words_save_ptr); - if (sid != NULL && port_in != NULL && port_out != NULL) { - rexmpp_jingle_call_accept(s, sid, atoi(port_in), atoi(port_out)); + if (sid != NULL) { + rexmpp_jingle_call_accept(s, sid); } } else if (! strcmp(word, "call")) { char *jid = strtok_r(NULL, " ", &words_save_ptr); - char *port_in = strtok_r(NULL, " ", &words_save_ptr); - char *port_out = strtok_r(NULL, " ", &words_save_ptr); - if (jid != NULL && port_in != NULL && port_out != NULL) { - rexmpp_jingle_call(s, jid, atoi(port_in), atoi(port_out)); + if (jid != NULL) { + rexmpp_jingle_call(s, jid); + } + } + } + + if (! strcmp(word, "disco")) { + word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + return; + } + if (! strcmp(word, "info")) { + char *jid = strtok_r(NULL, " ", &words_save_ptr); + if (jid == NULL) { + return; + } + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", + "http://jabber.org/protocol/disco#info"); + rexmpp_iq_new(s, "get", jid, query, rexmpp_console_disco_info, NULL); + } + if (! strcmp(word, "items")) { + char *jid = strtok_r(NULL, " ", &words_save_ptr); + if (jid == NULL) { + return; + } + rexmpp_xml_t *query = + rexmpp_xml_new_elem("query", + "http://jabber.org/protocol/disco#items"); + rexmpp_iq_new(s, "get", jid, query, rexmpp_console_disco_items, NULL); + } + } + + if (! strcmp(word, "pubsub")) { + word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + return; + } + if (! strcmp(word, "node")) { + word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + return; + } + if (! strcmp(word, "delete")) { + char *service_jid = strtok_r(NULL, " ", &words_save_ptr); + char *node = strtok_r(NULL, " ", &words_save_ptr); + if (service_jid == NULL || node == NULL) { + return; + } + rexmpp_pubsub_node_delete(s, service_jid, node, rexmpp_console_pubsub_node_deleted, NULL); + } + } + } + + if (! strcmp(word, "blocklist")) { + word = strtok_r(NULL, " ", &words_save_ptr); + if (word == NULL) { + rexmpp_xml_t *bl = + rexmpp_xml_new_elem("blocklist", "urn:xmpp:blocking"); + rexmpp_iq_new(s, "get", NULL, bl, rexmpp_console_blocklist, NULL); + } else if (! strcmp(word, "block")) { + char *jid = strtok_r(NULL, " ", &words_save_ptr); + if (jid == NULL) { + return; + } + rexmpp_xml_t *bl = + rexmpp_xml_new_elem("block", "urn:xmpp:blocking"); + rexmpp_xml_t *item = + rexmpp_xml_new_elem("item", "urn:xmpp:blocking"); + rexmpp_xml_add_attr(item, "jid", jid); + rexmpp_xml_add_child(bl, item); + rexmpp_iq_new(s, "set", NULL, bl, rexmpp_console_blocklist_blocked, NULL); + } else if (! strcmp(word, "unblock")) { + char *jid = strtok_r(NULL, " ", &words_save_ptr); + if (jid == NULL) { + return; } + rexmpp_xml_t *bl = + rexmpp_xml_new_elem("unblock", "urn:xmpp:blocking"); + rexmpp_xml_t *item = + rexmpp_xml_new_elem("item", "urn:xmpp:blocking"); + rexmpp_xml_add_attr(item, "jid", jid); + rexmpp_xml_add_child(bl, item); + rexmpp_iq_new(s, "set", NULL, bl, rexmpp_console_blocklist_unblocked, NULL); } } } diff --git a/src/rexmpp_console.h b/src/rexmpp_console.h index 85e3d75..bb2aed7 100644 --- a/src/rexmpp_console.h +++ b/src/rexmpp_console.h @@ -11,8 +11,8 @@ #include "rexmpp.h" -void rexmpp_console_on_send (rexmpp_t *s, xmlNodePtr node); -void rexmpp_console_on_recv (rexmpp_t *s, xmlNodePtr node); +void rexmpp_console_on_send (rexmpp_t *s, rexmpp_xml_t *node); +void rexmpp_console_on_recv (rexmpp_t *s, rexmpp_xml_t *node); void rexmpp_console_on_run (rexmpp_t *s, rexmpp_err_t result); void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len); diff --git a/src/rexmpp_digest.c b/src/rexmpp_digest.c new file mode 100644 index 0000000..c1dd436 --- /dev/null +++ b/src/rexmpp_digest.c @@ -0,0 +1,125 @@ +/** + @file rexmpp_digest.c + @brief Cryptographic functions + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#include "config.h" +#include "rexmpp_digest.h" + +#if defined(HAVE_GCRYPT) +#include <gcrypt.h> +#elif defined(HAVE_NETTLE) +#include <nettle/nettle-meta.h> +#include <stdlib.h> +#elif defined(HAVE_OPENSSL) +#include <openssl/evp.h> +#endif + +size_t rexmpp_digest_len (rexmpp_digest_algorithm algo) { + switch (algo) { + case REXMPP_DIGEST_SHA1: return 20; + case REXMPP_DIGEST_SHA256: return 32; + case REXMPP_DIGEST_SHA3_256: return 32; + default: return 0; + } +} + +int rexmpp_digest_buffer (rexmpp_digest_algorithm algo, + const void *in, + size_t in_len, + void *out, + size_t out_len) +{ + rexmpp_digest_t ctx; + int err = rexmpp_digest_init(&ctx, algo); + if (err) { + return err; + } + err = rexmpp_digest_update(&ctx, in, in_len); + if (err) { + return err; + } + return rexmpp_digest_finish(&ctx, out, out_len); +} + +int rexmpp_digest_init (rexmpp_digest_t *ctx, rexmpp_digest_algorithm algo) { +#if defined(HAVE_GCRYPT) + int gcry_algo = GCRY_MD_NONE; + switch (algo) { + case REXMPP_DIGEST_SHA1: gcry_algo = GCRY_MD_SHA1; break; + case REXMPP_DIGEST_SHA256: gcry_algo = GCRY_MD_SHA256; break; + case REXMPP_DIGEST_SHA3_256: gcry_algo = GCRY_MD_SHA3_256; break; + default: return -1; + } + gcry_error_t err = gcry_md_open(ctx, gcry_algo, 0); + if (err != GPG_ERR_NO_ERROR) { + return -1; + } +#elif defined(HAVE_NETTLE) + ctx->nh = NULL; + switch (algo) { + case REXMPP_DIGEST_SHA1: ctx->nh = &nettle_sha1; break; + case REXMPP_DIGEST_SHA256: ctx->nh = &nettle_sha256; break; + case REXMPP_DIGEST_SHA3_256: ctx->nh = &nettle_sha3_256; break; + default: return -1; + } + ctx->nh_ctx = malloc(ctx->nh->context_size); + ctx->nh->init(ctx->nh_ctx); +#elif defined(HAVE_OPENSSL) + const EVP_MD *md = NULL; + switch (algo) { + case REXMPP_DIGEST_SHA1: md = EVP_sha1(); break; + case REXMPP_DIGEST_SHA256: md = EVP_sha256(); break; + case REXMPP_DIGEST_SHA3_256: md = EVP_sha3_256(); break; + default: return -1; + } + *ctx = EVP_MD_CTX_new(); + if (! EVP_DigestInit(*ctx, md)) { + EVP_MD_CTX_free(*ctx); + return -1; + } +#endif + return 0; +} + +int rexmpp_digest_update (rexmpp_digest_t *ctx, const void *in, size_t len) { +#if defined(HAVE_GCRYPT) + gcry_md_write(*ctx, in, len); +#elif defined(HAVE_NETTLE) + ctx->nh->update(ctx->nh_ctx, len, in); +#elif defined(HAVE_OPENSSL) + if (! EVP_DigestUpdate(*ctx, in, len)) { + return -1; + } +#endif + return 0; +} + +int rexmpp_digest_finish (rexmpp_digest_t *ctx, void *out, size_t len) { + int ret = 0; +#if defined(HAVE_GCRYPT) + if (out != NULL) { + unsigned char *result = gcry_md_read(*ctx, 0); + if (result != NULL) { + memcpy(out, result, len); + } else { + ret = -1; + } + } + gcry_md_close(*ctx); +#elif defined(HAVE_NETTLE) + ctx->nh->digest(ctx->nh_ctx, len, out); + free(ctx->nh_ctx); +#elif defined(HAVE_OPENSSL) + (void)len; + if (! EVP_DigestFinal_ex(*ctx, out, NULL)) { + ret = -1; + } + EVP_MD_CTX_free(*ctx); + *ctx = NULL; +#endif + return ret; +} diff --git a/src/rexmpp_digest.h b/src/rexmpp_digest.h new file mode 100644 index 0000000..5001e12 --- /dev/null +++ b/src/rexmpp_digest.h @@ -0,0 +1,85 @@ +/** + @file rexmpp_digest.h + @brief Cryptographic functions + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_DIGEST_H +#define REXMPP_DIGEST_H + +typedef enum { + REXMPP_DIGEST_SHA1, + REXMPP_DIGEST_SHA256, + REXMPP_DIGEST_SHA3_256 +} rexmpp_digest_algorithm; + + +#if defined(HAVE_GCRYPT) +#include <gcrypt.h> +typedef gcry_md_hd_t rexmpp_digest_t; +#elif defined(HAVE_NETTLE) +#include <nettle/nettle-meta.h> +struct rexmpp_digest { + const struct nettle_hash *nh; + void *nh_ctx; +}; +typedef struct rexmpp_digest rexmpp_digest_t; +#elif defined(HAVE_OPENSSL) +#include <openssl/evp.h> +typedef EVP_MD_CTX* rexmpp_digest_t; +#endif + +/** + @brief Finds the digest length for a given algorithm. + @param[in] algo An algorithm. + @returns Digest length in bytes. +*/ +size_t rexmpp_digest_len (rexmpp_digest_algorithm algo); + +/** + @brief Computes a digest for a buffer. + @param[in] algo An algorithm. + @param[in] in Input data. + @param[in] in_len Input data length. + @param[out] out Output buffer. + @param[in] out_len Output buffer length. + @returns 0 on success, non-zero on failure. +*/ +int rexmpp_digest_buffer (rexmpp_digest_algorithm algo, + const void *in, + size_t in_len, + void *out, + size_t out_len); + +/** + @brief Initializes a digest context. + @param[out] ctx Pointer to an allocated ::rexmpp_digest_t context + to initialize. + @param[in] algo An algorithm to use. + @returns 0 on success, non-zero on failure. +*/ +int rexmpp_digest_init (rexmpp_digest_t *ctx, rexmpp_digest_algorithm algo); + +/** + @brief Updates a digest computation. + @param[in,out] ctx Context pointer. + @param[in] in Input data. + @param[in] len Length of the input buffer. + @returns 0 on success, non-zero on failure. +*/ +int rexmpp_digest_update (rexmpp_digest_t *ctx, const void *in, size_t len); + +/** + @brief Finishes a digest computation, freeing the context and + providing the output. + @param[in,out] ctx Context pointer. + @param[out] out A place to write the computed digest into, can be + NULL to just free the context. + @param[in] len Length of the allocated output buffer. + @returns 0 on success, non-zero on failure. +*/ +int rexmpp_digest_finish (rexmpp_digest_t *ctx, void *out, size_t len); + +#endif diff --git a/src/rexmpp_dns.c b/src/rexmpp_dns.c index 5c3f4f6..d56aa10 100644 --- a/src/rexmpp_dns.c +++ b/src/rexmpp_dns.c @@ -7,6 +7,7 @@ */ #include <memory.h> +#include <stdlib.h> #include <syslog.h> #include "config.h" @@ -64,6 +65,7 @@ int rexmpp_parse_srv (char *in, int in_len, struct rexmpp_dns_srv *out) { } +#ifndef USE_RUST void rexmpp_dns_result_free (rexmpp_dns_result_t *result) { if (result->data != NULL) { int i; @@ -79,20 +81,37 @@ void rexmpp_dns_result_free (rexmpp_dns_result_t *result) { } free(result); } +#endif rexmpp_dns_result_t *result_from_hostent (struct hostent *hostinfo) { rexmpp_dns_result_t *r = malloc(sizeof(rexmpp_dns_result_t)); + if (r == NULL) { + return NULL; + } r->secure = 0; int i, size = 0; while (hostinfo->h_addr_list[size] != NULL) { size++; } r->data = malloc(sizeof(void *) * (size + 1)); + if (r->data == NULL) { + free(r); + return NULL; + } r->len = malloc(sizeof(int) * size); + if (r->len == NULL) { + free(r->data); + free(r); + return NULL; + } for (i = 0; i < size; i++) { r->len[i] = hostinfo->h_length; r->data[i] = malloc(r->len[i]); - memcpy(r->data[i], hostinfo->h_addr_list[i], hostinfo->h_length); + if (r->data[i] != NULL) { + memcpy(r->data[i], hostinfo->h_addr_list[i], hostinfo->h_length); + } else { + return r; + } } r->data[size] = NULL; return r; @@ -102,22 +121,22 @@ rexmpp_dns_result_t *result_from_hostent (struct hostent *hostinfo) { int rexmpp_dns_ctx_init (rexmpp_t *s) { #if defined(USE_UNBOUND) int err; - s->resolver.ctx = ub_ctx_create(); - if (s->resolver.ctx == NULL) { + s->resolver = ub_ctx_create(); + if (s->resolver == NULL) { rexmpp_log(s, LOG_CRIT, "Failed to create resolver context"); return 1; } - err = ub_ctx_resolvconf(s->resolver.ctx, NULL); + err = ub_ctx_resolvconf(s->resolver, NULL); if (err != 0) { rexmpp_log(s, LOG_WARNING, "Failed to read resolv.conf: %s", ub_strerror(err)); } - err = ub_ctx_hosts(s->resolver.ctx, NULL); + err = ub_ctx_hosts(s->resolver, NULL); if (err != 0) { rexmpp_log(s, LOG_WARNING, "Failed to read hosts file: %s", ub_strerror(err)); } - err = ub_ctx_add_ta_file(s->resolver.ctx, DNSSEC_TRUST_ANCHOR_FILE); + err = ub_ctx_add_ta_file(s->resolver, DNSSEC_TRUST_ANCHOR_FILE); if (err != 0) { rexmpp_log(s, LOG_WARNING, "Failed to set root key file for DNSSEC: %s", ub_strerror(err)); @@ -130,7 +149,7 @@ int rexmpp_dns_ctx_init (rexmpp_t *s) { ares_strerror(err)); return 1; } - err = ares_init(&(s->resolver.channel)); + err = ares_init(&(s->resolver)); if (err) { rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s", ares_strerror(err)); @@ -139,7 +158,7 @@ int rexmpp_dns_ctx_init (rexmpp_t *s) { } return 0; #else - (void)s; + s->resolver = NULL; return 0; #endif } @@ -151,12 +170,15 @@ void rexmpp_dns_ctx_cleanup (rexmpp_t *s) { void rexmpp_dns_ctx_deinit (rexmpp_t *s) { #if defined(USE_UNBOUND) - if (s->resolver.ctx != NULL) { - ub_ctx_delete(s->resolver.ctx); - s->resolver.ctx = NULL; + if (s->resolver != NULL) { + ub_ctx_delete(s->resolver); + s->resolver = NULL; } #elif defined(USE_CARES) - ares_destroy(s->resolver.channel); + if (s->resolver != NULL) { + ares_destroy(s->resolver); + s->resolver = NULL; + } ares_library_cleanup(); #else (void)s; @@ -166,13 +188,13 @@ void rexmpp_dns_ctx_deinit (rexmpp_t *s) { int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { #if defined(USE_UNBOUND) (void)write_fds; - int max_fd = ub_fd(s->resolver.ctx) + 1; + int max_fd = ub_fd(s->resolver) + 1; if (max_fd != 0) { FD_SET(max_fd - 1, read_fds); } return max_fd; #elif defined(USE_CARES) - return ares_fds(s->resolver.channel, read_fds, write_fds); + return ares_fds(s->resolver, read_fds, write_fds); #else (void)s; (void)read_fds; @@ -181,16 +203,30 @@ int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { #endif } -struct timeval * rexmpp_dns_timeout (rexmpp_t *s, - struct timeval *max_tv, - struct timeval *tv) +struct timespec * rexmpp_dns_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv) { #if defined(USE_UNBOUND) (void)s; (void)tv; return max_tv; #elif defined(USE_CARES) - return ares_timeout(s->resolver.channel, max_tv, tv); + struct timeval tv_ms; + struct timeval *max_tv_ms = NULL; + if (max_tv != NULL) { + max_tv_ms = &tv_ms; + tv_ms.tv_sec = tv->tv_sec; + tv_ms.tv_usec = tv->tv_nsec / 1000; + } + struct timeval *ret_ms = ares_timeout(s->resolver, max_tv_ms, &tv_ms); + if (ret_ms == max_tv_ms) { + return max_tv; + } else { + tv->tv_sec = tv_ms.tv_sec; + tv->tv_nsec = tv_ms.tv_usec * 1000; + return tv; + } #else (void)s; (void)max_tv; @@ -237,20 +273,54 @@ void rexmpp_dns_cb (void *ptr, size++; } rexmpp_dns_result_t *res = malloc(sizeof(rexmpp_dns_result_t)); - res->data = malloc(sizeof(void *) * (size + 1)); + if (res == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result"); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + res->data = calloc((size + 1), sizeof(void *)); + if (res->data == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result's data"); + free(res); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } res->len = malloc(sizeof(int) * size); + if (res->len == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result's len"); + free(res->data); + free(res); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } for (i = 0; i < size; i++) { if (result->qtype == 33) { /* SRV */ res->len[i] = sizeof(rexmpp_dns_srv_t); res->data[i] = malloc(res->len[i]); + if (res->data[i] == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to allocate memory for an SRV record"); + d->cb(s, d->ptr, res); + rexmpp_dns_result_free(res); + free(d); + return; + } int err = rexmpp_parse_srv(result->data[i], result->len[i], (rexmpp_dns_srv_t*)res->data[i]); if (err) { rexmpp_log(s, LOG_WARNING, "Failed to parse an SRV record"); - res->data[i + 1] = NULL; + d->cb(s, d->ptr, res); rexmpp_dns_result_free(res); - d->cb(s, d->ptr, NULL); free(d); return; } @@ -258,7 +328,12 @@ void rexmpp_dns_cb (void *ptr, /* Non-SRV, for now that's just A or AAAA */ res->len[i] = result->len[i]; res->data[i] = malloc(res->len[i]); - memcpy(res->data[i], result->data[i], res->len[i]); + if (res->data[i] == NULL) { + rexmpp_log(s, LOG_WARNING, + "Failed to allocate memory for a DNS result's data record"); + } else { + memcpy(res->data[i], result->data[i], res->len[i]); + } } } res->data[size] = NULL; @@ -332,10 +407,14 @@ int rexmpp_dns_resolve (rexmpp_t *s, #if defined(USE_UNBOUND) struct rexmpp_dns_query_cb_data *d = malloc(sizeof(struct rexmpp_dns_query_cb_data)); + if (d == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query"); + return 1; + } d->s = s; d->cb = callback; d->ptr = ptr; - int err = ub_resolve_async(s->resolver.ctx, query, rrtype, rrclass, + int err = ub_resolve_async(s->resolver, query, rrtype, rrclass, d, rexmpp_dns_cb, NULL); if (err) { rexmpp_log(s, LOG_ERR, "Failed to query %s: %s", @@ -345,10 +424,14 @@ int rexmpp_dns_resolve (rexmpp_t *s, #elif defined(USE_CARES) struct rexmpp_dns_query_cb_data *d = malloc(sizeof(struct rexmpp_dns_query_cb_data)); + if (d == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query"); + return 1; + } d->s = s; d->cb = callback; d->ptr = ptr; - ares_query(s->resolver.channel, query, rrclass, rrtype, rexmpp_dns_cb, d); + ares_query(s->resolver, query, rrclass, rrtype, rexmpp_dns_cb, d); #else if (rrclass == 1) { if (rrtype == 1 || rrtype == 28) { @@ -381,8 +464,8 @@ int rexmpp_dns_process (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { #if defined(USE_UNBOUND) (void)read_fds; (void)write_fds; - if (ub_poll(s->resolver.ctx)) { - int err = ub_process(s->resolver.ctx); + if (ub_poll(s->resolver)) { + int err = ub_process(s->resolver); if (err != 0) { rexmpp_log(s, LOG_ERR, "DNS query processing error: %s", ub_strerror(err)); @@ -391,7 +474,7 @@ int rexmpp_dns_process (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } return 0; #elif defined(USE_CARES) - ares_process(s->resolver.channel, read_fds, write_fds); + ares_process(s->resolver, read_fds, write_fds); return 0; #else (void)s; diff --git a/src/rexmpp_dns.h b/src/rexmpp_dns.h index 6641238..7abd2ef 100644 --- a/src/rexmpp_dns.h +++ b/src/rexmpp_dns.h @@ -12,6 +12,7 @@ #define REXMPP_DNS_H #include <stdint.h> +#include <stdbool.h> #include "config.h" #include "rexmpp.h" @@ -21,21 +22,21 @@ */ #if defined(USE_UNBOUND) #include <unbound.h> -struct rexmpp_dns_ctx { - struct ub_ctx *ctx; -}; +typedef struct ub_ctx* rexmpp_dns_ctx_t; +/* struct rexmpp_dns_ctx { */ +/* struct ub_ctx *ctx; */ +/* }; */ #elif defined(USE_CARES) #include <ares.h> -struct rexmpp_dns_ctx { - ares_channel channel; -}; +typedef ares_channel rexmpp_dns_ctx_t; +/* struct rexmpp_dns_ctx { */ +/* ares_channel channel; */ +/* }; */ #else -struct rexmpp_dns_ctx { - int dummy; -}; +typedef void* rexmpp_dns_ctx_t; #endif -typedef struct rexmpp_dns_ctx rexmpp_dns_ctx_t; +/* typedef struct rexmpp_dns_ctx rexmpp_dns_ctx_t; */ struct rexmpp_dns_srv { uint16_t priority; @@ -58,7 +59,7 @@ struct rexmpp_dns_result { int *len; /** @brief Whether the result was retrieved securely (that is, verified with DNSSEC). */ - int secure; + bool secure; }; typedef struct rexmpp_dns_result rexmpp_dns_result_t; @@ -103,9 +104,9 @@ int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds); /** @brief Reports timeouts. */ -struct timeval * rexmpp_dns_timeout (rexmpp_t *s, - struct timeval *max_tv, - struct timeval *tv); +struct timespec * rexmpp_dns_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv); typedef void (*dns_query_cb_t) (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result); diff --git a/src/rexmpp_dns.rs b/src/rexmpp_dns.rs new file mode 100644 index 0000000..1489835 --- /dev/null +++ b/src/rexmpp_dns.rs @@ -0,0 +1,62 @@ +use std::os::raw::{c_int, c_void, c_char}; +use libc::*; +use std::ptr; + +use super::rexmpp; + +type DNSQueryCB = unsafe extern "C" +fn (s: *mut rexmpp::Rexmpp, ptr: *mut c_void, result: *mut RexmppDNSResult) -> (); + +extern { + pub fn rexmpp_dns_resolve (s: *mut rexmpp::Rexmpp, + query: *const c_char, + rrtype: c_int, + rrclass: c_int, + ptr: *mut c_void, + callback: DNSQueryCB) -> c_int; + pub fn rexmpp_dns_process (s: *mut rexmpp::Rexmpp, + read_fds: *mut fd_set, + write_fds: *mut fd_set) -> c_int; + pub fn rexmpp_dns_fds (s: *mut rexmpp::Rexmpp, + read_fds: *mut fd_set, + write_fds: *mut fd_set) -> c_int; + pub fn rexmpp_dns_timeout (s: *mut rexmpp::Rexmpp, + max_tv: *mut timespec, + tv: *mut timespec) -> *mut timespec; +} + +#[repr(C)] +pub struct RexmppDNSResult { + pub data: *mut *mut c_void, + pub len: *mut c_int, + pub secure: bool +} + +#[repr(C)] +pub struct RexmppDNSSRV { + pub priority: u16, + pub weight: u16, + pub port: u16, + pub target: [c_char; 256] +} + + +#[no_mangle] +pub unsafe extern "C" +fn rexmpp_dns_result_free (result: *mut RexmppDNSResult) { + if (*result).data != ptr::null_mut() { + let mut i = 0; + let data_ptr: *mut *mut c_void = (*result).data; + while *(data_ptr.offset(i)) != ptr::null_mut() { + free(*(data_ptr.offset(i))); + i += 1; + } + free((*result).data as *mut c_void); + (*result).data = ptr::null_mut(); + } + if (*result).len != ptr::null_mut() { + free((*result).len as *mut c_void); + (*result).len = ptr::null_mut(); + } + free(result as *mut c_void); +} diff --git a/src/rexmpp_http_upload.c b/src/rexmpp_http_upload.c index 62d371b..18a3302 100644 --- a/src/rexmpp_http_upload.c +++ b/src/rexmpp_http_upload.c @@ -10,6 +10,7 @@ #include <string.h> #include <libgen.h> #include <errno.h> +#include <stdlib.h> #include "config.h" @@ -18,6 +19,7 @@ #endif #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_http_upload.h" @@ -43,46 +45,51 @@ void rexmpp_upload_task_finish (struct rexmpp_http_upload_task *task) { void rexmpp_http_upload_slot_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; struct rexmpp_http_upload_task *task = ptr; if (success) { - xmlNodePtr slot = rexmpp_xml_find_child(response, "urn:xmpp:http:upload:0", "slot"); - xmlNodePtr put = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "put"); - xmlNodePtr get = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "get"); + rexmpp_xml_t *slot = rexmpp_xml_find_child(response, "urn:xmpp:http:upload:0", "slot"); + rexmpp_xml_t *put = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "put"); + rexmpp_xml_t *get = rexmpp_xml_find_child(slot, "urn:xmpp:http:upload:0", "get"); if (put != NULL && get != NULL) { - char *put_url = xmlGetProp(put, "url"); - char *get_url = xmlGetProp(get, "url"); + const char *put_url = rexmpp_xml_find_attr_val(put, "url"); + const char *get_url = rexmpp_xml_find_attr_val(get, "url"); if (put_url != NULL && get_url != NULL) { - task->get_url = get_url; + task->get_url = strdup(get_url); CURL *ce = curl_easy_init(); curl_easy_setopt(ce, CURLOPT_PRIVATE, task); curl_easy_setopt(ce, CURLOPT_UPLOAD, 1); curl_easy_setopt(ce, CURLOPT_READDATA, task->fh); curl_easy_setopt(ce, CURLOPT_URL, put_url); + curl_easy_setopt(ce, CURLOPT_INFILESIZE, task->fsize); - xmlNodePtr header = xmlFirstElementChild(put); + rexmpp_xml_t *header = rexmpp_xml_first_elem_child(put); while (header) { - char *header_name = xmlGetProp(header, "name"); + const char *header_name = rexmpp_xml_find_attr_val(header, "name"); if (header_name != NULL) { - char *header_str = xmlNodeGetContent(header); + const char *header_str = rexmpp_xml_text_child(header); if (header_str != NULL) { - size_t full_header_str_len = strlen(header_name) + 3 + strlen(header_str); + size_t full_header_str_len = + strlen(header_name) + 3 + strlen(header_str); char *full_header_str = malloc(full_header_str_len); - snprintf(full_header_str, full_header_str_len, "%s: %s", - header_name, header_str); - task->http_headers = - curl_slist_append(task->http_headers, full_header_str); - free(full_header_str); - free(header_str); + if (full_header_str != NULL) { + snprintf(full_header_str, full_header_str_len, "%s: %s", + header_name, header_str); + task->http_headers = + curl_slist_append(task->http_headers, full_header_str); + free(full_header_str); + } else { + rexmpp_log(s, LOG_ERR, + "Failed to allocate memory for a header"); + } } - free(header_name); } - header = header->next; + header = rexmpp_xml_next_elem_sibling(header); } curl_easy_setopt(ce, CURLOPT_HTTPHEADER, task->http_headers); @@ -91,12 +98,6 @@ void rexmpp_http_upload_slot_cb (rexmpp_t *s, return; } else { rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot."); - if (get_url != NULL) { - free(get_url); - } - } - if (put_url != NULL) { - free(put_url); } } else { rexmpp_log(s, LOG_ERR, "Unexpected structure for a HTTP file upload slot."); @@ -109,8 +110,8 @@ void rexmpp_http_upload_slot_cb (rexmpp_t *s, void rexmpp_http_upload_feature_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)response; @@ -120,17 +121,17 @@ void rexmpp_http_upload_feature_cb (rexmpp_t *s, rexmpp_upload_task_finish(task); return; } - xmlNodePtr req = rexmpp_xml_new_node("request", "urn:xmpp:http:upload:0"); - xmlNewProp(req, "filename", task->fname); + rexmpp_xml_t *req = + rexmpp_xml_new_elem("request", "urn:xmpp:http:upload:0"); + rexmpp_xml_add_attr(req, "filename", task->fname); char buf[11]; snprintf(buf, 11, "%u", task->fsize); - xmlNewProp(req, "size", buf); + rexmpp_xml_add_attr(req, "size", buf); if (task->content_type) { - xmlNewProp(req, "content-type", task->content_type); + rexmpp_xml_add_attr(req, "content-type", task->content_type); } - char *to = xmlGetProp(request, "to"); + const char *to = rexmpp_xml_find_attr_val(request, "to"); rexmpp_iq_new(s, "get", to, req, rexmpp_http_upload_slot_cb, task); - free(to); } rexmpp_err_t @@ -143,8 +144,18 @@ rexmpp_http_upload (rexmpp_t *s, http_upload_cb cb, void *cb_data) { + if (fname == NULL) { + rexmpp_log(s, LOG_ERR, "No file name is provided"); + fclose(fh); + return REXMPP_E_PARAM; + } struct rexmpp_http_upload_task *task = malloc(sizeof(struct rexmpp_http_upload_task)); + if (task == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for an upload task"); + fclose(fh); + return REXMPP_E_MALLOC; + } task->fname = strdup(fname); task->fsize = fsize; task->fh = fh; diff --git a/src/rexmpp_jid.c b/src/rexmpp_jid.c index 522a492..f1c371c 100644 --- a/src/rexmpp_jid.c +++ b/src/rexmpp_jid.c @@ -64,8 +64,10 @@ int rexmpp_jid_parse (const char *str, struct rexmpp_jid *jid) { jid->local[local_len] = '\0'; strncpy(jid->domain, domain, domain_len); jid->domain[domain_len] = '\0'; - strncpy(jid->resource, resource, resource_len); - jid->resource[resource_len] = '\0'; + if (resource != NULL) { + strncpy(jid->resource, resource, resource_len); + jid->resource[resource_len] = '\0'; + } return 0; } diff --git a/src/rexmpp_jid.rs b/src/rexmpp_jid.rs new file mode 100644 index 0000000..7730b2f --- /dev/null +++ b/src/rexmpp_jid.rs @@ -0,0 +1,17 @@ +use std::os::raw::{c_char}; + +#[repr(C)] +pub struct RexmppJID { + local: [c_char; 1024], + domain: [c_char; 1024], + resource: [c_char; 1024], + bare: [c_char; 2048], + full: [c_char; 3072] +} + +// #[no_mangle] +// extern "C" +// fn rexmpp_jid_parse (str: *const c_char, jid : &mut RexmppJID) -> c_int +// { + +// } diff --git a/src/rexmpp_jingle.c b/src/rexmpp_jingle.c index 3a72a95..2b84445 100644 --- a/src/rexmpp_jingle.c +++ b/src/rexmpp_jingle.c @@ -27,24 +27,251 @@ A/V calls over ICE-UDP + DTLS-SRTP: #include <syslog.h> #include <errno.h> #include <libgen.h> -#include <gcrypt.h> #include "config.h" #ifdef ENABLE_CALLS +#include <inttypes.h> #include <glib.h> #include <gio/gnetworking.h> #include <nice.h> #include <agent.h> -#include <gnutls/dtls.h> -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> #include <srtp2/srtp.h> +#include <math.h> +#include "portaudio.h" +#ifdef HAVE_OPUS +#include <opus.h> +#endif #endif #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_jingle.h" #include "rexmpp_base64.h" +#include "rexmpp_random.h" +#include "rexmpp_tls.h" +#include "rexmpp_digest.h" + + +/* https://en.wikipedia.org/wiki/G.711 */ +uint8_t rexmpp_pcma_encode (int16_t x) { + uint8_t sign = 0x80; + uint8_t pos; + uint8_t val = 0; + if (x < 0) { + x = -x; + sign = 0; + } + x >>= 3; + for (pos = 11; pos > 5 && ! (x & (1 << pos)); pos--); + val = (x >> (pos - 4)) & 0xf; + return (sign | ((pos - 4) << 4) | val) ^ 0x55; +} + +int16_t rexmpp_pcma_decode (uint8_t x) { + x ^= 0x55; + int16_t sign = -1; + uint8_t shift = (x >> 4) & 7; + int16_t val = x & 0xf; + if (x & 0x80) { + x ^= 0x80; + sign = 1; + } + val = (val << 1) | 1; + if (shift > 0) { + val = (val | 0x20) << (shift - 1); + } + val <<= 3; + return sign * val; +} + +uint8_t rexmpp_pcmu_encode (int16_t x) { + uint8_t sign = 0; + uint8_t pos; + uint8_t val = 0; + if (x < 0) { + x = -x; + sign = 0x80; + } + x >>= 2; + for (pos = 12; pos > 5 && ! (x & (1 << pos)); pos--); + val = (x >> (pos - 4)) & 0xf; + return (sign | ((pos - 4) << 4) | val) ^ 0xff; +} + +int16_t rexmpp_pcmu_decode (uint8_t x) { + x ^= 0xff; + int16_t sign = 1; + uint8_t shift = (x >> 4) & 7; + int16_t val = x & 0xf; + if (x & 0x80) { + x ^= 0x80; + sign = -1; + } + val = (val << 1) | 1; + if (shift > 0) { + val = (val | 0x20) << (shift - 1); + } + val <<= 2; + return sign * val; +} + +rexmpp_xml_t * +rexmpp_jingle_session_payload_by_id (rexmpp_jingle_session_t *sess, + int payload_type_id) +{ + if (sess->accept == NULL) { + return NULL; + } + rexmpp_xml_t *descr_child = + rexmpp_xml_first_elem_child + (rexmpp_xml_find_child + (rexmpp_xml_find_child + (sess->accept, "urn:xmpp:jingle:1", "content"), + "urn:xmpp:jingle:apps:rtp:1", "description")); + while (descr_child != NULL) { + if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1", + "payload-type")) + { + const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id"); + if (pl_id != NULL && atoi(pl_id) == payload_type_id) { + return descr_child; + } + } + descr_child = rexmpp_xml_next_elem_sibling(descr_child); + } + return NULL; +} + +#ifdef ENABLE_CALLS +int rexmpp_jingle_session_configure_audio (rexmpp_jingle_session_t *sess) { + if (sess->accept == NULL) { + return 0; + } + rexmpp_xml_t *descr_child = + rexmpp_xml_first_elem_child + (rexmpp_xml_find_child + (rexmpp_xml_find_child(sess->accept, "urn:xmpp:jingle:1", "content"), + "urn:xmpp:jingle:apps:rtp:1", "description")); + while (descr_child != NULL) { + if (rexmpp_xml_match(descr_child, "urn:xmpp:jingle:apps:rtp:1", + "payload-type")) + { + const char *pl_id = rexmpp_xml_find_attr_val(descr_child, "id"); + if (pl_id != NULL) { + if (atoi(pl_id) == 0) { + rexmpp_log(sess->s, LOG_INFO, + "Setting the codec to PCMU (0) for Jingle session %s", + sess->sid); + sess->codec = REXMPP_CODEC_PCMU; + sess->payload_type = 0; + return sess->payload_type; + } + if (atoi(pl_id) == 8) { + rexmpp_log(sess->s, LOG_INFO, + "Setting the codec to PCMA (8) for Jingle session %s", + sess->sid); + sess->codec = REXMPP_CODEC_PCMA; + sess->payload_type = 8; + return sess->payload_type; + } +#ifdef HAVE_OPUS + const char *pl_name = rexmpp_xml_find_attr_val(descr_child, "name"); + if ((pl_name != NULL) && (strcmp(pl_name, "opus") == 0)) { + sess->codec = REXMPP_CODEC_OPUS; + sess->payload_type = atoi(pl_id); + rexmpp_log(sess->s, LOG_INFO, + "Setting the codec to Opus (%u) for Jingle session %s", + sess->payload_type, sess->sid); + return sess->payload_type; + } +#endif /* HAVE_OPUS */ + } + } + descr_child = rexmpp_xml_next_elem_sibling(descr_child); + } + return 0; +} + + +static int rexmpp_pa_callback (const void *input, + void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData) +{ + (void)timeInfo; + (void)statusFlags; + struct pa_buffers *data = (struct pa_buffers*)userData; + int16_t *out = (int16_t*)output; + int16_t *in = (int16_t*)input; + unsigned int i; + for (i = 0; i < frameCount; i++) { + if (in != NULL) { + data->capture.buf[data->capture.write_pos] = in[i]; + data->capture.write_pos++; + data->capture.write_pos %= PA_BUF_SIZE; + } + + if (data->playback.read_pos != data->playback.write_pos) { + out[i] = data->playback.buf[data->playback.read_pos]; + data->playback.read_pos++; + data->playback.read_pos %= PA_BUF_SIZE; + } else { + out[i] = 0; + } + } + return 0; +} + +void +rexmpp_jingle_run_audio (rexmpp_jingle_session_t *sess) { + rexmpp_random_buf(&(sess->rtp_seq_num), sizeof(uint16_t)); + sess->rtp_last_seq_num = 0xffff; + rexmpp_random_buf(&(sess->rtp_timestamp), sizeof(uint32_t)); + rexmpp_random_buf(&(sess->rtp_ssrc), sizeof(uint32_t)); + + int rate = 8000; + int channels = 1; +#ifdef HAVE_OPUS + if (sess->codec == REXMPP_CODEC_OPUS) { + rate = 48000; + channels = 2; + int opus_error; + sess->opus_enc = + opus_encoder_create(rate, channels, OPUS_APPLICATION_VOIP, &opus_error); + sess->opus_dec = + opus_decoder_create(rate, channels, &opus_error); + } +#endif /* HAVE_OPUS */ + + rexmpp_log(sess->s, LOG_DEBUG, + "Setting up an audio stream: SSRC %" PRIx32 + ", %d Hz, %d channels", + sess->rtp_ssrc, rate, channels); + + PaError err = Pa_OpenDefaultStream (&(sess->pa_stream), + channels, + channels, + paInt16, + /* paFloat32, */ + rate, + /* 480, */ + paFramesPerBufferUnspecified, + rexmpp_pa_callback, + &(sess->ring_buffers)); + if (err != paNoError) { + rexmpp_log(sess->s, LOG_ERR, "Failed to open a PA stream: %s", + Pa_GetErrorText(err)); + } + err = Pa_StartStream(sess->pa_stream); + if (err != paNoError) { + rexmpp_log(sess->s, LOG_ERR, "Failed to start a PA stream: %s", + Pa_GetErrorText(err)); + } +} +#endif /* ENABLE_CALLS */ rexmpp_jingle_session_t * @@ -52,7 +279,7 @@ rexmpp_jingle_session_by_id (rexmpp_t *s, const char *sid) { if (sid == NULL) { return NULL; } - rexmpp_jingle_session_t *cur = s->jingle.sessions; + rexmpp_jingle_session_t *cur = s->jingle->sessions; while (cur != NULL) { if (strcmp(cur->sid, sid) == 0) { return cur; @@ -71,17 +298,36 @@ void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) { free(session->sid); } if (session->initiate != NULL) { - xmlFreeNodeList(session->initiate); + rexmpp_xml_free_list(session->initiate); } if (session->accept != NULL) { - xmlFreeNodeList(session->accept); + rexmpp_xml_free_list(session->accept); } if (session->ibb_fh != NULL) { fclose(session->ibb_fh); } - #ifdef ENABLE_CALLS +#ifdef ENABLE_CALLS if (session->type == REXMPP_JINGLE_SESSION_MEDIA) { int i; + if (session->pa_stream != NULL) { + PaError err = Pa_StopStream(session->pa_stream); + if (err != paNoError) { + rexmpp_log(session->s, LOG_ERR, + "Failed to close a PortAudio stream: %s", + Pa_GetErrorText(err)); + } + session->pa_stream = NULL; + } +#ifdef HAVE_OPUS + if (session->opus_enc != NULL) { + opus_encoder_destroy(session->opus_enc); + session->opus_enc = NULL; + } + if (session->opus_dec != NULL) { + opus_decoder_destroy(session->opus_dec); + session->opus_dec = NULL; + } +#endif /* HAVE_OPUS */ for (i = 0; i < 2; i++) { rexmpp_jingle_component_t *comp = &session->component[i]; if (comp->dtls_state == REXMPP_TLS_ACTIVE || @@ -90,22 +336,29 @@ void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) { /* SRTP structures are allocated upon a TLS connection, so using the TLS state to find when they should be deallocated. */ - srtp_dealloc(comp->srtp_in); - srtp_dealloc(comp->srtp_out); + if (comp->srtp_in != NULL) { + srtp_dealloc(comp->srtp_in); + } + if (comp->srtp_out != NULL) { + srtp_dealloc(comp->srtp_out); + } } if (comp->dtls_state == REXMPP_TLS_HANDSHAKE || comp->dtls_state == REXMPP_TLS_ACTIVE || comp->dtls_state == REXMPP_TLS_CLOSING || comp->dtls_state == REXMPP_TLS_CLOSED) { - gnutls_deinit(comp->dtls_session); + if (comp->dtls_state == REXMPP_TLS_HANDSHAKE || + comp->dtls_state == REXMPP_TLS_ACTIVE) { + rexmpp_tls_disconnect(comp->s, comp->dtls); + } + rexmpp_tls_session_free(comp->dtls); + rexmpp_tls_ctx_free(comp->dtls); + comp->dtls = NULL; comp->dtls_state = REXMPP_TLS_INACTIVE; } - if (comp->udp_socket != -1) { - close(comp->udp_socket); - comp->udp_socket = -1; - } } if (session->ice_agent != NULL) { + nice_agent_close_async(session->ice_agent, NULL, NULL); g_object_unref(session->ice_agent); session->ice_agent = NULL; } @@ -126,7 +379,7 @@ void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) { session->turn_password = NULL; } } - #endif +#endif /* ENABLE_CALLS */ free(session); } @@ -135,11 +388,11 @@ void rexmpp_jingle_session_delete (rexmpp_t *s, rexmpp_jingle_session_t *sess) { return; } rexmpp_log(s, LOG_DEBUG, "Removing Jingle session %s", sess->sid); - rexmpp_jingle_session_t *cur = s->jingle.sessions, *prev = NULL; + rexmpp_jingle_session_t *cur = s->jingle->sessions, *prev = NULL; while (cur != NULL) { if (sess == cur) { if (prev == NULL) { - s->jingle.sessions = cur->next; + s->jingle->sessions = cur->next; } else { prev->next = cur->next; } @@ -157,7 +410,7 @@ void rexmpp_jingle_session_delete_by_id (rexmpp_t *s, const char *sid) { int rexmpp_jingle_session_add (rexmpp_t *s, rexmpp_jingle_session_t *sess) { uint32_t sessions_num = 0; - rexmpp_jingle_session_t *cur = s->jingle.sessions; + rexmpp_jingle_session_t *cur = s->jingle->sessions; while (cur != NULL) { sessions_num++; cur = cur->next; @@ -168,8 +421,8 @@ int rexmpp_jingle_session_add (rexmpp_t *s, rexmpp_jingle_session_t *sess) { return 0; } rexmpp_log(s, LOG_DEBUG, "Adding Jingle session %s", sess->sid); - sess->next = s->jingle.sessions; - s->jingle.sessions = sess; + sess->next = s->jingle->sessions; + s->jingle->sessions = sess; return 1; } @@ -184,45 +437,61 @@ rexmpp_jingle_session_create (rexmpp_t *s, { rexmpp_jingle_session_t *sess = malloc(sizeof(rexmpp_jingle_session_t)); if (sess != NULL) { - sess->s = s; sess->jid = jid; sess->sid = sid; - sess->type = type; - sess->initiator = initiator; sess->initiate = NULL; sess->accept = NULL; + sess->s = s; + sess->initiator = initiator; + sess->type = type; sess->ibb_fh = NULL; sess->ibb_sid = NULL; sess->ibb_seq = 0; #ifdef ENABLE_CALLS + sess->stun_host = NULL; + sess->stun_port = 0; + sess->turn_host = NULL; + sess->turn_port = 0; + sess->turn_username = NULL; + sess->turn_password = NULL; + int i; for (i = 0; i < 2; i++) { sess->component[i].component_id = i + 1; sess->component[i].session = sess; sess->component[i].s = s; sess->component[i].dtls_state = REXMPP_TLS_INACTIVE; - sess->component[i].dtls_buf_len = 0; - sess->component[i].udp_socket = -1; + sess->component[i].dtls = rexmpp_tls_ctx_new(s, 1); + sess->component[i].srtp_out = NULL; + sess->component[i].srtp_in = NULL; } - sess->ice_agent = NULL; + sess->rtcp_mux = s->jingle_prefer_rtcp_mux; + sess->ice_agent = NULL; - sess->stun_host = NULL; - sess->stun_port = 0; - sess->turn_host = NULL; - sess->turn_port = 0; - sess->turn_username = NULL; - sess->turn_password = NULL; - /* rexmpp_jingle_ice_agent_init(sess); */ -#endif + sess->pa_stream = NULL; + + sess->ring_buffers.capture.read_pos = 0; + sess->ring_buffers.capture.write_pos = 0; + sess->ring_buffers.playback.read_pos = 0; + sess->ring_buffers.playback.write_pos = 0; + sess->codec = REXMPP_CODEC_UNDEFINED; + sess->payload_type = 0; + +#ifdef HAVE_OPUS + sess->opus_enc = NULL; + sess->opus_dec = NULL; +#endif /* HAVE_POUS */ +#endif /* ENABLE_CALLS */ if (! rexmpp_jingle_session_add(s, sess)) { - rexmpp_jingle_session_destroy(sess); - sess = NULL; + /* The session is destroyed by rexmpp_jingle_session_add */ + return NULL; } + return sess; } else { rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a Jingle session"); + return NULL; } - return sess; } rexmpp_jingle_session_t * @@ -230,7 +499,7 @@ rexmpp_jingle_session_by_ibb_sid (rexmpp_t *s, const char *ibb_sid) { if (ibb_sid == NULL) { return NULL; } - rexmpp_jingle_session_t *cur = s->jingle.sessions; + rexmpp_jingle_session_t *cur = s->jingle->sessions; while (cur != NULL) { if (cur->type == REXMPP_JINGLE_SESSION_FILE && strcmp(cur->ibb_sid, ibb_sid) == 0) { @@ -244,31 +513,37 @@ rexmpp_jingle_session_by_ibb_sid (rexmpp_t *s, const char *ibb_sid) { } int rexmpp_jingle_init (rexmpp_t *s) { - s->jingle.sessions = NULL; + s->jingle = malloc(sizeof(struct rexmpp_jingle_ctx)); + s->jingle->sessions = NULL; #ifdef ENABLE_CALLS g_networking_init(); srtp_init(); - s->jingle.gloop = g_main_loop_new(NULL, FALSE); -#endif + s->jingle->gloop = g_main_loop_new(NULL, FALSE); + Pa_Initialize(); + nice_debug_enable(1); +#endif /* ENABLE_CALLS */ return 0; } void rexmpp_jingle_stop (rexmpp_t *s) { - while (s->jingle.sessions != NULL) { - rexmpp_jingle_session_delete(s, s->jingle.sessions); + while (s->jingle->sessions != NULL) { + rexmpp_jingle_session_delete(s, s->jingle->sessions); } #ifdef ENABLE_CALLS - g_main_loop_quit(s->jingle.gloop); - s->jingle.gloop = NULL; + g_main_loop_quit(s->jingle->gloop); + s->jingle->gloop = NULL; srtp_shutdown(); -#endif + Pa_Terminate(); +#endif /* ENABLE_CALLS */ + free(s->jingle); + s->jingle = NULL; } void rexmpp_jingle_accept_file_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -292,15 +567,16 @@ rexmpp_jingle_accept_file (rexmpp_t *s, path, strerror(errno)); return REXMPP_E_OTHER; } - xmlNodePtr jingle = session->initiate; - xmlNodePtr content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); + rexmpp_xml_t *jingle = session->initiate; + rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); - xmlNodePtr new_jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); - xmlNewProp(new_jingle, "action", "session-accept"); - xmlNewProp(new_jingle, "responder", s->assigned_jid.full); - xmlNewProp(new_jingle, "sid", session->sid); - xmlAddChild(new_jingle, xmlCopyNode(content, 1)); - session->accept = xmlCopyNode(new_jingle, 1); + rexmpp_xml_t *new_jingle = + rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(new_jingle, "action", "session-accept"); + rexmpp_xml_add_attr(new_jingle, "responder", s->assigned_jid.full); + rexmpp_xml_add_attr(new_jingle, "sid", session->sid); + rexmpp_xml_add_child(new_jingle, rexmpp_xml_clone(content)); + session->accept = rexmpp_xml_clone(new_jingle); return rexmpp_iq_new(s, "set", session->jid, new_jingle, rexmpp_jingle_accept_file_cb, strdup(session->sid)); } @@ -319,8 +595,8 @@ rexmpp_jingle_accept_file_by_id (rexmpp_t *s, void rexmpp_jingle_session_terminate_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -336,24 +612,27 @@ void rexmpp_jingle_session_terminate_cb (rexmpp_t *s, rexmpp_err_t rexmpp_jingle_session_terminate (rexmpp_t *s, const char *sid, - xmlNodePtr reason_node, + rexmpp_xml_t *reason_node, const char *reason_text) { rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid); if (session == NULL) { return REXMPP_E_OTHER; } - xmlNodePtr jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); - xmlNewProp(jingle, "action", "session-terminate"); - xmlNewProp(jingle, "sid", sid); - xmlNodePtr reason = rexmpp_xml_new_node("reason", "urn:xmpp:jingle:1"); + rexmpp_xml_t *jingle = + rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(jingle, "action", "session-terminate"); + rexmpp_xml_add_attr(jingle, "sid", sid); + rexmpp_xml_t *reason = + rexmpp_xml_new_elem("reason", "urn:xmpp:jingle:1"); if (reason_text != NULL) { - xmlNodePtr text = rexmpp_xml_new_node("text", "urn:xmpp:jingle:1"); - xmlNodeAddContent(text, reason_text); - xmlAddChild(reason, text); + rexmpp_xml_t *text = + rexmpp_xml_new_elem("text", "urn:xmpp:jingle:1"); + rexmpp_xml_add_text(text, reason_text); + rexmpp_xml_add_child(reason, text); } - xmlAddChild(reason, reason_node); - xmlAddChild(jingle, reason); + rexmpp_xml_add_child(reason, reason_node); + rexmpp_xml_add_child(jingle, reason); rexmpp_err_t ret = rexmpp_iq_new(s, "set", session->jid, jingle, rexmpp_jingle_session_terminate_cb, strdup(sid)); @@ -363,8 +642,8 @@ rexmpp_jingle_session_terminate (rexmpp_t *s, void rexmpp_jingle_send_file_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -391,95 +670,99 @@ rexmpp_jingle_send_file (rexmpp_t *s, } char buf[4096]; - gcry_md_hd_t hd; - gcry_error_t err = gcry_md_open(&hd, GCRY_MD_SHA256, 0); - if (err != GPG_ERR_NO_ERROR) { - rexmpp_log(s, LOG_ERR, "Failed to create a MD object: %s", - gcry_strerror(err)); + rexmpp_digest_t sha256, sha3_256; + if (rexmpp_digest_init(&sha256, REXMPP_DIGEST_SHA256)) { + rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-256 digest object"); fclose(fh); return REXMPP_E_OTHER; } - err = gcry_md_enable(hd, GCRY_MD_SHA3_256); - if (err != GPG_ERR_NO_ERROR) { - rexmpp_log(s, LOG_ERR, "Failed to add sha3-256 to the MD object: %s", - gcry_strerror(err)); + if (rexmpp_digest_init(&sha3_256, REXMPP_DIGEST_SHA3_256)) { + rexmpp_log(s, LOG_ERR, "Failed to initialize a SHA-3-256 digest object"); + rexmpp_digest_finish(&sha256, NULL, 0); fclose(fh); return REXMPP_E_OTHER; } size_t len = fread(buf, 1, 4096, fh); while (len > 0) { - gcry_md_write(hd, buf, len); + rexmpp_digest_update(&sha256, buf, len); + rexmpp_digest_update(&sha3_256, buf, len); len = fread(buf, 1, 4096, fh); } - gcry_md_final(hd); - - char *sid = rexmpp_gen_id(s); - char *ibb_sid = rexmpp_gen_id(s); - - xmlNodePtr jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); - xmlNewProp(jingle, "action", "session-initiate"); - xmlNewProp(jingle, "sid", sid); - xmlNewProp(jingle, "initiator", s->assigned_jid.full); - - xmlNodePtr content = rexmpp_xml_new_node("content", "urn:xmpp:jingle:1"); - xmlNewProp(content, "creator", "initiator"); - xmlNewProp(content, "name", "IBB file"); - xmlAddChild(jingle, content); - - xmlNodePtr transport = - rexmpp_xml_new_node("transport", "urn:xmpp:jingle:transports:ibb:1"); - xmlNewProp(transport, "block-size", "4096"); - xmlNewProp(transport, "sid", ibb_sid); - xmlAddChild(content, transport); - xmlNodePtr description = - rexmpp_xml_new_node("description", "urn:xmpp:jingle:apps:file-transfer:5"); - xmlAddChild(content, description); - xmlNodePtr file = - rexmpp_xml_new_node("file", "urn:xmpp:jingle:apps:file-transfer:5"); - xmlAddChild(description, file); - xmlNodePtr file_name = - rexmpp_xml_new_node("name", "urn:xmpp:jingle:apps:file-transfer:5"); - xmlNodeAddContent(file_name, basename(path)); - xmlAddChild(file, file_name); + char *sid = rexmpp_random_id(); + char *ibb_sid = rexmpp_random_id(); + + rexmpp_xml_t *jingle = + rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(jingle, "action", "session-initiate"); + rexmpp_xml_add_attr(jingle, "sid", sid); + rexmpp_xml_add_attr(jingle, "initiator", s->assigned_jid.full); + + rexmpp_xml_t *content = + rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(content, "creator", "initiator"); + rexmpp_xml_add_attr(content, "name", "IBB file"); + rexmpp_xml_add_child(jingle, content); + + rexmpp_xml_t *transport = + rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ibb:1"); + rexmpp_xml_add_attr(transport, "block-size", "4096"); + rexmpp_xml_add_attr(transport, "sid", ibb_sid); + rexmpp_xml_add_child(content, transport); + rexmpp_xml_t *description = + rexmpp_xml_new_elem("description", "urn:xmpp:jingle:apps:file-transfer:5"); + rexmpp_xml_add_child(content, description); + rexmpp_xml_t *file = + rexmpp_xml_new_elem("file", "urn:xmpp:jingle:apps:file-transfer:5"); + rexmpp_xml_add_child(description, file); + rexmpp_xml_t *file_name = + rexmpp_xml_new_elem("name", "urn:xmpp:jingle:apps:file-transfer:5"); + rexmpp_xml_add_text(file_name, basename(path)); + rexmpp_xml_add_child(file, file_name); + + char hash[32]; char *hash_base64 = NULL; size_t hash_base64_len = 0; - rexmpp_base64_to(gcry_md_read(hd, GCRY_MD_SHA256), - gcry_md_get_algo_dlen(GCRY_MD_SHA256), + + rexmpp_digest_finish(&sha256, hash, + rexmpp_digest_len(REXMPP_DIGEST_SHA256)); + rexmpp_base64_to(hash, + rexmpp_digest_len(REXMPP_DIGEST_SHA256), &hash_base64, &hash_base64_len); - xmlNodePtr file_hash = rexmpp_xml_new_node("hash", "urn:xmpp:hashes:2"); - xmlNewProp(file_hash, "algo", "sha-256"); - xmlNodeAddContent(file_hash, hash_base64); + rexmpp_xml_t *file_hash = + rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2"); + rexmpp_xml_add_attr(file_hash, "algo", "sha-256"); + rexmpp_xml_add_text(file_hash, hash_base64); free(hash_base64); - xmlAddChild(file, file_hash); + rexmpp_xml_add_child(file, file_hash); hash_base64 = NULL; hash_base64_len = 0; - rexmpp_base64_to(gcry_md_read(hd, GCRY_MD_SHA3_256), - gcry_md_get_algo_dlen(GCRY_MD_SHA3_256), + rexmpp_digest_finish(&sha3_256, hash, + rexmpp_digest_len(REXMPP_DIGEST_SHA3_256)); + rexmpp_base64_to(hash, + rexmpp_digest_len(REXMPP_DIGEST_SHA3_256), &hash_base64, &hash_base64_len); - file_hash = rexmpp_xml_new_node("hash", "urn:xmpp:hashes:2"); - xmlNewProp(file_hash, "algo", "sha3-256"); - xmlNodeAddContent(file_hash, hash_base64); + file_hash = rexmpp_xml_new_elem("hash", "urn:xmpp:hashes:2"); + rexmpp_xml_add_attr(file_hash, "algo", "sha3-256"); + rexmpp_xml_add_text(file_hash, hash_base64); free(hash_base64); - xmlAddChild(file, file_hash); - - gcry_md_close(hd); + rexmpp_xml_add_child(file, file_hash); long fsize = ftell(fh); fseek(fh, 0, SEEK_SET); snprintf(buf, 11, "%ld", fsize); - xmlNodePtr file_size = - rexmpp_xml_new_node("size", "urn:xmpp:jingle:apps:file-transfer:5"); - xmlNodeAddContent(file_size, buf); - xmlAddChild(file, file_size); + rexmpp_xml_t *file_size = + rexmpp_xml_new_elem("size", "urn:xmpp:jingle:apps:file-transfer:5"); + rexmpp_xml_add_text(file_size, buf); + rexmpp_xml_add_child(file, file_size); rexmpp_jingle_session_t *sess = rexmpp_jingle_session_create(s, strdup(jid), sid, REXMPP_JINGLE_SESSION_FILE, 1); if (sess != NULL) { - sess->initiate = xmlCopyNode(jingle, 1); + sess->initiate = rexmpp_xml_clone(jingle); sess->ibb_sid = ibb_sid; sess->ibb_fh = fh; return rexmpp_iq_new(s, "set", sess->jid, jingle, @@ -492,8 +775,8 @@ rexmpp_jingle_send_file (rexmpp_t *s, void rexmpp_jingle_ibb_close_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -509,8 +792,8 @@ void rexmpp_jingle_ibb_close_cb (rexmpp_t *s, void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -529,8 +812,9 @@ void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, return; } if (feof(session->ibb_fh)) { - xmlNodePtr close = rexmpp_xml_new_node("close", "http://jabber.org/protocol/ibb"); - xmlNewProp(close, "sid", session->ibb_sid); + rexmpp_xml_t *close = + rexmpp_xml_new_elem("close", "http://jabber.org/protocol/ibb"); + rexmpp_xml_add_attr(close, "sid", session->ibb_sid); rexmpp_iq_new(s, "set", session->jid, close, rexmpp_jingle_ibb_close_cb, sid); return; @@ -538,15 +822,16 @@ void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, char buf[4096]; size_t len = fread(buf, 1, 4096, session->ibb_fh); if (len > 0) { - xmlNodePtr data = rexmpp_xml_new_node("data", "http://jabber.org/protocol/ibb"); - xmlNewProp(data, "sid", session->ibb_sid); + rexmpp_xml_t *data = + rexmpp_xml_new_elem("data", "http://jabber.org/protocol/ibb"); + rexmpp_xml_add_attr(data, "sid", session->ibb_sid); char *out = NULL; size_t out_len = 0; rexmpp_base64_to(buf, len, &out, &out_len); - xmlNodeAddContent(data, out); + rexmpp_xml_add_text(data, out); free(out); snprintf(buf, 11, "%u", session->ibb_seq); - xmlNewProp(data, "seq", buf); + rexmpp_xml_add_attr(data, "seq", buf); session->ibb_seq++; rexmpp_iq_new(s, "set", session->jid, data, rexmpp_jingle_ibb_send_cb, sid); @@ -554,7 +839,7 @@ void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, } else { rexmpp_log(s, LOG_ERR, "Failed to read from a file: %s ", strerror(errno)); rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("media-error", + rexmpp_xml_new_elem("media-error", "urn:xmpp:jingle:1"), "File reading error"); } @@ -565,8 +850,8 @@ void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, #ifdef ENABLE_CALLS void rexmpp_jingle_call_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -581,31 +866,29 @@ void rexmpp_jingle_call_cb (rexmpp_t *s, void rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess, - xmlNodePtr transport) + rexmpp_xml_t *transport) { if (sess->ice_agent == NULL) { /* Must be an incoming call; just add candidates to session-initiate's transport. */ - xmlNodePtr old_transport = + rexmpp_xml_t *old_transport = rexmpp_xml_find_child(rexmpp_xml_find_child(sess->initiate, "urn:xmpp:jingle:1", "content"), "urn:xmpp:jingle:transports:ice-udp:1", "transport"); - xmlNodePtr candidate = xmlFirstElementChild(transport); + rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport); while (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1", "candidate")) { - xmlAddChild(old_transport, xmlCopyNode(candidate, 1)); - candidate = candidate->next; + rexmpp_xml_add_child(old_transport, rexmpp_xml_clone(candidate)); + candidate = rexmpp_xml_next_elem_sibling(candidate); } return; } - char *ufrag = xmlGetProp(transport, "ufrag"); - char *password = xmlGetProp(transport, "pwd"); + const char *ufrag = rexmpp_xml_find_attr_val(transport, "ufrag"); + const char *password = rexmpp_xml_find_attr_val(transport, "pwd"); nice_agent_set_remote_credentials(sess->ice_agent, sess->ice_stream_id, ufrag, password); - free(ufrag); - free(password); int component_id; @@ -614,59 +897,63 @@ rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess, nice_agent_get_remote_candidates(sess->ice_agent, sess->ice_stream_id, component_id); - xmlNodePtr candidate = xmlFirstElementChild(transport); - while (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1", - "candidate")) { - char *component = xmlGetProp(candidate, "component"); - if (component[0] == component_id + '0') { - char *type_str = xmlGetProp(candidate, "type"); - int type_n = NICE_CANDIDATE_TYPE_HOST; - if (strcmp(type_str, "host") == 0) { - type_n = NICE_CANDIDATE_TYPE_HOST; - } else if (strcmp(type_str, "srflx") == 0) { - type_n = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; - } else if (strcmp(type_str, "prflx") == 0) { - type_n = NICE_CANDIDATE_TYPE_PEER_REFLEXIVE; - } else if (strcmp(type_str, "relay") == 0) { - type_n = NICE_CANDIDATE_TYPE_RELAYED; - } - free(type_str); - NiceCandidate *c = nice_candidate_new(type_n); - c->component_id = component_id; - c->stream_id = sess->ice_stream_id; + rexmpp_xml_t *candidate = rexmpp_xml_first_elem_child(transport); + while (candidate != NULL) { + if (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1", + "candidate")) { + const char *component = rexmpp_xml_find_attr_val(candidate, "component"); + if (component[0] == component_id + '0') { + const char *type_str = rexmpp_xml_find_attr_val(candidate, "type"); + int type_n = NICE_CANDIDATE_TYPE_HOST; + if (strcmp(type_str, "host") == 0) { + type_n = NICE_CANDIDATE_TYPE_HOST; + } else if (strcmp(type_str, "srflx") == 0) { + type_n = NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE; + } else if (strcmp(type_str, "prflx") == 0) { + type_n = NICE_CANDIDATE_TYPE_PEER_REFLEXIVE; + } else if (strcmp(type_str, "relay") == 0) { + type_n = NICE_CANDIDATE_TYPE_RELAYED; + } + NiceCandidate *c = nice_candidate_new(type_n); + c->component_id = component_id; + c->stream_id = sess->ice_stream_id; - char *foundation = xmlGetProp(candidate, "foundation"); - strncpy(c->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION - 1); - c->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0; - free(foundation); + const char *foundation = rexmpp_xml_find_attr_val(candidate, "foundation"); + strncpy(c->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION - 1); + c->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0; - c->transport = NICE_CANDIDATE_TRANSPORT_UDP; + c->transport = NICE_CANDIDATE_TRANSPORT_UDP; - char *priority = xmlGetProp(candidate, "priority"); - c->priority = atoi(priority); - free(priority); + const char *priority = rexmpp_xml_find_attr_val(candidate, "priority"); + c->priority = atoi(priority); - char *ip = xmlGetProp(candidate, "ip"); - if (! nice_address_set_from_string(&c->addr, ip)) { - rexmpp_log(sess->s, LOG_ERR, - "Failed to parse an ICE-UDP candidate's address: %s", - ip); - } - free(ip); + const char *ip = rexmpp_xml_find_attr_val(candidate, "ip"); + if (! nice_address_set_from_string(&c->addr, ip)) { + rexmpp_log(sess->s, LOG_ERR, + "Failed to parse an ICE-UDP candidate's address: %s", + ip); + } - char *port = xmlGetProp(candidate, "port"); - nice_address_set_port(&c->addr, atoi(port)); - free(port); + const char *port = rexmpp_xml_find_attr_val(candidate, "port"); + nice_address_set_port(&c->addr, atoi(port)); - remote_candidates = g_slist_prepend(remote_candidates, c); + remote_candidates = g_slist_prepend(remote_candidates, c); + } } - free(component); - candidate = candidate->next; + candidate = rexmpp_xml_next_elem_sibling(candidate); } if (remote_candidates != NULL) { + rexmpp_log(sess->s, LOG_DEBUG, + "Setting %d remote candidates for component %d", + g_slist_length(remote_candidates), + component_id); nice_agent_set_remote_candidates(sess->ice_agent, sess->ice_stream_id, component_id, remote_candidates); g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free); + } else { + rexmpp_log(sess->s, LOG_WARNING, + "No remote candidates for component %d", + component_id); } } } @@ -674,7 +961,7 @@ rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess, /* Checks whether we are in the active (client) role for DTLS, based on either "session-initiate" or "session-accept" message. */ int rexmpp_jingle_dtls_is_active (rexmpp_jingle_session_t *sess, int in_initiate) { - xmlNodePtr fingerprint = + rexmpp_xml_t *fingerprint = rexmpp_xml_find_child (rexmpp_xml_find_child (rexmpp_xml_find_child @@ -687,7 +974,7 @@ int rexmpp_jingle_dtls_is_active (rexmpp_jingle_session_t *sess, int in_initiate in_initiate ? "initiate" : "accept"); return 0; } - char *fingerprint_setup = xmlGetProp(fingerprint, "setup"); + const char *fingerprint_setup = rexmpp_xml_find_attr_val(fingerprint, "setup"); if (fingerprint_setup == NULL) { rexmpp_log(sess->s, LOG_ERR, "No 'setup' attribute for a fingerprint element"); return 0; @@ -706,15 +993,14 @@ int rexmpp_jingle_dtls_is_active (rexmpp_jingle_session_t *sess, int in_initiate active = (strcmp(fingerprint_setup, "active") == 0); } } - free(fingerprint_setup); return active; } void rexmpp_transport_info_call_cb (rexmpp_t *s, void *ptr, - xmlNodePtr request, - xmlNodePtr response, + rexmpp_xml_t *request, + rexmpp_xml_t *response, int success) { (void)request; @@ -735,126 +1021,110 @@ rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, { rexmpp_jingle_session_t *sess = data; - gnutls_x509_crt_t *cert_list; - int cert_list_size = 0; - /* We'll need a certificate a bit later, but checking it before - allocating other things. */ - int err = gnutls_certificate_get_x509_crt(sess->s->jingle.dtls_cred, 0, - &cert_list, &cert_list_size); - if (err) { - rexmpp_log(sess->s, LOG_ERR, - "Failed to read own certificate list: %s", - gnutls_strerror(err)); - return; - } + rexmpp_log(sess->s, LOG_DEBUG, "ICE agent candidate gathering is done"); - char fp[32], fp_str[97]; + /* We'll need a fingerprint a bit later, but checking it before + allocating other things. */ + char fp[32], fp_str[32 * 3 + 1]; size_t fp_size = 32; - gnutls_x509_crt_get_fingerprint(cert_list[0], GNUTLS_DIG_SHA256, fp, &fp_size); - int i; - for (i = 0; i < 32; i++) { - snprintf(fp_str + i * 3, 4, "%02X:", fp[i] & 0xFF); - } - fp_str[95] = 0; - - for (i = 0; i < cert_list_size; i++) { - gnutls_x509_crt_deinit(cert_list[i]); + if (rexmpp_tls_my_fp(sess->s, fp, fp_str, &fp_size)) { + return; } - gnutls_free(cert_list); - xmlNodePtr jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); - xmlNewProp(jingle, "sid", sess->sid); + rexmpp_xml_t *jingle = rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(jingle, "sid", sess->sid); - xmlNodePtr content = rexmpp_xml_new_node("content", "urn:xmpp:jingle:1"); - xmlNewProp(content, "creator", "initiator"); - xmlNewProp(content, "senders", "both"); - xmlNodePtr description; + rexmpp_xml_t *content = rexmpp_xml_new_elem("content", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(content, "creator", "initiator"); + rexmpp_xml_add_attr(content, "senders", "both"); + rexmpp_xml_t *description; if (sess->initiator) { - xmlNewProp(jingle, "action", "session-initiate"); - xmlNewProp(jingle, "initiator", sess->s->assigned_jid.full); - xmlNewProp(content, "name", "call"); + /* We are the intiator: send the predefined options. */ + rexmpp_xml_add_attr(jingle, "action", "session-initiate"); + rexmpp_xml_add_attr(jingle, "initiator", sess->s->assigned_jid.full); + rexmpp_xml_add_attr(content, "name", "call"); /* https://datatracker.ietf.org/doc/html/rfc4568 */ - xmlNodePtr encryption = - rexmpp_xml_new_node("encryption", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(encryption, "required", "true"); - xmlAddChild(content, encryption); - xmlNodePtr crypto = rexmpp_xml_new_node("crypto", "urn:xmpp:jingle:apps:rtp:1"); - xmlNewProp(crypto, "crypto-suite", "AES_CM_128_HMAC_SHA1_80"); - xmlNewProp(crypto, "tag", "1"); - xmlAddChild(encryption, crypto); - - description = xmlCopyNode(sess->s->jingle_rtp_description, 1); + rexmpp_xml_t *encryption = + rexmpp_xml_new_elem("encryption", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(encryption, "required", "true"); + rexmpp_xml_add_child(content, encryption); + rexmpp_xml_t *crypto = + rexmpp_xml_new_elem("crypto", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_attr(crypto, "crypto-suite", "AES_CM_128_HMAC_SHA1_80"); + rexmpp_xml_add_attr(crypto, "tag", "1"); + rexmpp_xml_add_child(encryption, crypto); + + description = rexmpp_xml_clone(sess->s->jingle_rtp_description); } else { - xmlNodePtr init_jingle = sess->initiate; - xmlNodePtr init_content = + /* Accepting the call, will have to compare payload type options + to supported and preferred ones, and pick some from those. */ + rexmpp_xml_t *init_jingle = sess->initiate; + rexmpp_xml_t *init_content = rexmpp_xml_find_child(init_jingle, "urn:xmpp:jingle:1", "content"); - char *init_content_name = xmlGetProp(init_content, "name"); + const char *init_content_name = rexmpp_xml_find_attr_val(init_content, "name"); if (init_content_name != NULL) { - xmlNewProp(content, "name", init_content_name); - free(init_content_name); + rexmpp_xml_add_attr(content, "name", init_content_name); } else { rexmpp_log(sess->s, LOG_ERR, "Empty content name for Jingle session %s with %s", sess->sid, sess->jid); } - xmlNewProp(jingle, "action", "session-accept"); - xmlNewProp(jingle, "initiator", sess->jid); - xmlNewProp(jingle, "responder", sess->s->assigned_jid.full); + rexmpp_xml_add_attr(jingle, "action", "session-accept"); + rexmpp_xml_add_attr(jingle, "initiator", sess->jid); + rexmpp_xml_add_attr(jingle, "responder", sess->s->assigned_jid.full); - description = xmlCopyNode(sess->s->jingle_rtp_description, 2); + description = rexmpp_xml_clone(sess->s->jingle_rtp_description); /* Find the first matching payload-type and add that */ - xmlNodePtr pl_type = - xmlFirstElementChild(sess->s->jingle_rtp_description); - xmlNodePtr selected_pl = NULL; - while (pl_type != NULL && selected_pl == NULL) { + rexmpp_xml_t *pl_type = + rexmpp_xml_first_elem_child(sess->s->jingle_rtp_description); + rexmpp_xml_t *selected_pl = NULL; + while (pl_type != NULL) { if (rexmpp_xml_match(pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) { - char *pl_id = xmlGetProp(pl_type, "id"); + const char *pl_id = rexmpp_xml_find_attr_val(pl_type, "id"); if (pl_id != NULL) { int pl_id_num = atoi(pl_id); - xmlNodePtr proposed_pl_type = - xmlFirstElementChild + rexmpp_xml_t *proposed_pl_type = + rexmpp_xml_first_elem_child (rexmpp_xml_find_child (rexmpp_xml_find_child(sess->initiate, "urn:xmpp:jingle:1", "content"), "urn:xmpp:jingle:apps:rtp:1", "description")); while (proposed_pl_type != NULL && selected_pl == NULL) { if (rexmpp_xml_match(proposed_pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) { - char *proposed_pl_id = xmlGetProp(proposed_pl_type, "id"); + const char *proposed_pl_id = rexmpp_xml_find_attr_val(proposed_pl_type, "id"); if (proposed_pl_id != NULL) { int proposed_pl_id_num = atoi(proposed_pl_id); if (pl_id_num < 96 && pl_id_num == proposed_pl_id_num) { selected_pl = pl_type; } else { - char *pl_name = xmlGetProp(pl_type, "name"); + const char *pl_name = + rexmpp_xml_find_attr_val(pl_type, "name"); if (pl_name != NULL) { - char *proposed_pl_name = xmlGetProp(proposed_pl_type, "name"); + const char *proposed_pl_name = + rexmpp_xml_find_attr_val(proposed_pl_type, "name"); if (proposed_pl_name != NULL) { if (strcmp(pl_name, proposed_pl_name) == 0) { /* todo: compare clock rates, numbers of channels, parameters */ selected_pl = pl_type; } - free(proposed_pl_name); } - free(pl_name); } } - free(proposed_pl_id); } } - proposed_pl_type = proposed_pl_type->next; + proposed_pl_type = rexmpp_xml_next_elem_sibling(proposed_pl_type); } - free(pl_id); } else { rexmpp_log(sess->s, LOG_ERR, - "No 'id' specified for a pyaload-type element."); + "No 'id' specified for a payload-type element."); } } pl_type = pl_type->next; } if (selected_pl != NULL) { - xmlAddChild(description, xmlCopyNode(selected_pl, 1)); + rexmpp_xml_add_child(description, rexmpp_xml_clone(selected_pl)); } else { rexmpp_log(sess->s, LOG_ERR, "No suitable payload type found"); /* todo: fail if it's NULL, though it shouldn't happen, since @@ -862,69 +1132,70 @@ rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, } } - xmlAddChild(jingle, content); - xmlAddChild(content, description); + rexmpp_xml_add_child(jingle, content); + rexmpp_xml_add_child(content, description); if (sess->rtcp_mux) { - xmlNodePtr rtcp_mux = - rexmpp_xml_new_node("rtcp-mux", "urn:xmpp:jingle:apps:rtp:1"); - xmlAddChild(description, rtcp_mux); + rexmpp_xml_t *rtcp_mux = + rexmpp_xml_new_elem("rtcp-mux", "urn:xmpp:jingle:apps:rtp:1"); + rexmpp_xml_add_child(description, rtcp_mux); } - xmlNodePtr transport = - rexmpp_xml_new_node("transport", "urn:xmpp:jingle:transports:ice-udp:1"); + rexmpp_xml_t *transport = + rexmpp_xml_new_elem("transport", "urn:xmpp:jingle:transports:ice-udp:1"); gchar *ufrag = NULL; gchar *password = NULL; nice_agent_get_local_credentials(agent, stream_id, &ufrag, &password); - xmlNewProp(transport, "ufrag", ufrag); - xmlNewProp(transport, "pwd", password); + rexmpp_xml_add_attr(transport, "ufrag", ufrag); + rexmpp_xml_add_attr(transport, "pwd", password); g_free(ufrag); g_free(password); - xmlAddChild(content, transport); + rexmpp_xml_add_child(content, transport); int component_id; - xmlNodePtr postponed_candidates = NULL; + rexmpp_xml_t *postponed_candidates = NULL; for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) { GSList *candidates = nice_agent_get_local_candidates(agent, stream_id, component_id); GSList *cand_cur = candidates; int cand_num = 0; while (cand_cur != NULL) { - xmlNodePtr candidate = - rexmpp_xml_new_node("candidate", "urn:xmpp:jingle:transports:ice-udp:1"); + rexmpp_xml_t *candidate = + rexmpp_xml_new_elem("candidate", "urn:xmpp:jingle:transports:ice-udp:1"); char buf[INET6_ADDRSTRLEN]; NiceCandidate *c = (NiceCandidate *)cand_cur->data; snprintf(buf, 11, "%u", component_id); - xmlNewProp(candidate, "component", buf); - xmlNewProp(candidate, "foundation", c->foundation); - xmlNewProp(candidate, "generation", "0"); - char *cid = rexmpp_gen_id(sess->s); - xmlNewProp(candidate, "id", cid); + rexmpp_xml_add_attr(candidate, "component", buf); + rexmpp_xml_add_attr(candidate, "foundation", c->foundation); + rexmpp_xml_add_attr(candidate, "generation", "0"); + char *cid = rexmpp_random_id(); + rexmpp_xml_add_attr(candidate, "id", cid); free(cid); nice_address_to_string(&c->addr, buf); - xmlNewProp(candidate, "ip", buf); + rexmpp_xml_add_attr(candidate, "ip", buf); snprintf(buf, 11, "%u", nice_address_get_port(&c->addr)); - xmlNewProp(candidate, "port", buf); - xmlNewProp(candidate, "network", "0"); - xmlNewProp(candidate, "protocol", "udp"); + rexmpp_xml_add_attr(candidate, "port", buf); + rexmpp_xml_add_attr(candidate, "network", "0"); + rexmpp_xml_add_attr(candidate, "protocol", "udp"); snprintf(buf, 11, "%u", c->priority); - xmlNewProp(candidate, "priority", buf); + rexmpp_xml_add_attr(candidate, "priority", buf); char *nice_type[] = {"host", "srflx", "prflx", "relay"}; if (c->type < 4) { - xmlNewProp(candidate, "type", nice_type[c->type]); + rexmpp_xml_add_attr(candidate, "type", nice_type[c->type]); } /* Can't send too many candidates, since stanza sizes are usually - limited, and then it breaks the stream. Limiting to 10 per - component, sending the rest later, via transport-info. */ + limited, and then it breaks the stream. Limiting to 10 per + component, sending the rest later, via transport-info. */ if (cand_num < 10) { - xmlAddChild(transport, candidate); + rexmpp_xml_add_child(transport, candidate); } else { - xmlNodePtr jingle_ti = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); - xmlNewProp(jingle_ti, "sid", sess->sid); - xmlNewProp(jingle_ti, "action", "transport-info"); - xmlNodePtr content_copy = xmlCopyNode(content, 2); - xmlNodePtr transport_copy = xmlCopyNode(transport, 2); - xmlAddChild(jingle_ti, content_copy); - xmlAddChild(content_copy, transport_copy); - xmlAddChild(transport_copy, candidate); + rexmpp_xml_t *jingle_ti = + rexmpp_xml_new_elem("jingle", "urn:xmpp:jingle:1"); + rexmpp_xml_add_attr(jingle_ti, "sid", sess->sid); + rexmpp_xml_add_attr(jingle_ti, "action", "transport-info"); + rexmpp_xml_t *content_copy = rexmpp_xml_clone(content); + rexmpp_xml_t *transport_copy = rexmpp_xml_clone(transport); + rexmpp_xml_add_child(jingle_ti, content_copy); + rexmpp_xml_add_child(content_copy, transport_copy); + rexmpp_xml_add_child(transport_copy, candidate); jingle_ti->next = postponed_candidates; postponed_candidates = jingle_ti; } @@ -936,24 +1207,26 @@ rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, } } - xmlNodePtr fingerprint = - rexmpp_xml_new_node("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); - xmlNewProp(fingerprint, "hash", "sha-256"); + rexmpp_xml_t *fingerprint = + rexmpp_xml_new_elem("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + rexmpp_xml_add_attr(fingerprint, "hash", "sha-256"); if (sess->initiator) { - xmlNewProp(fingerprint, "setup", "actpass"); + rexmpp_xml_add_attr(fingerprint, "setup", "actpass"); } else if (rexmpp_jingle_dtls_is_active(sess, 1)) { - xmlNewProp(fingerprint, "setup", "active"); + rexmpp_xml_add_attr(fingerprint, "setup", "active"); } else { - xmlNewProp(fingerprint, "setup", "passive"); + rexmpp_xml_add_attr(fingerprint, "setup", "passive"); } - xmlNodeAddContent(fingerprint, fp_str); - xmlAddChild(transport, fingerprint); + rexmpp_xml_add_text(fingerprint, fp_str); + rexmpp_xml_add_child(transport, fingerprint); if (sess->initiator) { - sess->initiate = xmlCopyNode(jingle, 1); + sess->initiate = rexmpp_xml_clone(jingle); } else { - sess->accept = xmlCopyNode(jingle, 1); + sess->accept = rexmpp_xml_clone(jingle); + rexmpp_jingle_session_configure_audio(sess); + rexmpp_jingle_run_audio(sess); } rexmpp_iq_new(sess->s, "set", sess->jid, jingle, @@ -962,7 +1235,7 @@ rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, /* Now send transport-info messages with candidates that didn't fit initially. */ while (postponed_candidates != NULL) { - xmlNodePtr pc_next = postponed_candidates->next; + rexmpp_xml_t *pc_next = postponed_candidates->next; postponed_candidates->next = NULL; rexmpp_iq_new(sess->s, "set", sess->jid, postponed_candidates, rexmpp_transport_info_call_cb, strdup(sess->sid)); @@ -971,7 +1244,7 @@ rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, } ssize_t -rexmpp_jingle_dtls_push_func (gnutls_transport_ptr_t p, const void *data, size_t size) +rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size) { rexmpp_jingle_component_t *comp = p; rexmpp_jingle_session_t *sess = comp->session; @@ -979,58 +1252,21 @@ rexmpp_jingle_dtls_push_func (gnutls_transport_ptr_t p, const void *data, size_t comp->component_id, size, data); } -ssize_t rexmpp_jingle_dtls_generic_pull_func (rexmpp_jingle_session_t *sess, - char *tls_buf, - size_t *tls_buf_len, - gnutls_session_t tls_session, - void *data, - size_t size) -{ - (void)sess; - size_t ret = -1; - if (*tls_buf_len > 0) { - if (size >= *tls_buf_len) { - memcpy(data, tls_buf, *tls_buf_len); - ret = *tls_buf_len; - *tls_buf_len = 0; - } else { - if (size > DTLS_SRTP_BUF_SIZE) { - size = DTLS_SRTP_BUF_SIZE; - } - memcpy(data, tls_buf, size); - memmove(tls_buf, tls_buf + size, DTLS_SRTP_BUF_SIZE - size); - ret = size; - *tls_buf_len = *tls_buf_len - size; - } - } else { - gnutls_transport_set_errno(tls_session, EAGAIN); - ret = -1; - } - - return ret; +rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p) { + rexmpp_jingle_component_t *comp = p; + return comp->dtls; } -ssize_t -rexmpp_jingle_dtls_pull_func (gnutls_transport_ptr_t p, - void *data, - size_t size) +/* The timeout is always zero for DTLS. */ +int rexmpp_jingle_dtls_pull_timeout_func (void *p, + unsigned int ms) { rexmpp_jingle_component_t *comp = p; rexmpp_jingle_session_t *sess = comp->session; - return - rexmpp_jingle_dtls_generic_pull_func(sess, - comp->dtls_buf, - &comp->dtls_buf_len, - comp->dtls_session, - data, - size); -} + /* if (comp->dtls->dtls_buf_len > 0) { */ + /* return comp->dtls->dtls_buf_len; */ + /* } */ -int -rexmpp_jingle_dtls_generic_pull_timeout_func (rexmpp_jingle_session_t *sess, - unsigned int ms, - guint component_id) -{ fd_set rfds; struct timeval tv; @@ -1040,38 +1276,72 @@ rexmpp_jingle_dtls_generic_pull_timeout_func (rexmpp_jingle_session_t *sess, char c; FD_ZERO(&rfds); - - GSocket *sock = - nice_agent_get_selected_socket(sess->ice_agent, - sess->ice_stream_id, component_id); - int fd = g_socket_get_fd(sock); - FD_SET(fd, &rfds); + int fd, nfds = -1; + + GPtrArray *sockets = + nice_agent_get_sockets(sess->ice_agent, + sess->ice_stream_id, comp->component_id); + guint i; + for (i = 0; i < sockets->len; i++) { + fd = g_socket_get_fd(sockets->pdata[i]); + FD_SET(fd, &rfds); + if (fd > nfds) { + nfds = fd; + } + } + g_ptr_array_unref(sockets); tv.tv_sec = ms / 1000; tv.tv_usec = (ms % 1000) * 1000; - ret = select(fd + 1, &rfds, NULL, NULL, &tv); - if (ret <= 0) { + ret = select(nfds + 1, &rfds, NULL, NULL, &tv); + if (ret < 0) { + rexmpp_log(sess->s, LOG_WARNING, + "DTLS pull function: select failed: %s", + strerror(errno)); return ret; } cli_addr_size = sizeof(cli_addr); - ret = - recvfrom(fd, &c, 1, MSG_PEEK, - (struct sockaddr *) &cli_addr, &cli_addr_size); - if (ret > 0) { - return 1; + ret = 0; + sockets = + nice_agent_get_sockets(sess->ice_agent, + sess->ice_stream_id, comp->component_id); + for (i = 0; i < sockets->len; i++) { + int err = + recvfrom(g_socket_get_fd(sockets->pdata[i]), &c, 1, MSG_PEEK, + (struct sockaddr *) &cli_addr, &cli_addr_size); + if (err == -1) { + /* ENOTCONN and EAGAIN are common here, but report other + errors. */ + if (errno != ENOTCONN && errno != EAGAIN) { + rexmpp_log(sess->s, LOG_WARNING, + "DTLS pull function: failed to peek a socket: %s", + strerror(errno)); + } + } else { + ret += err; + } } + g_ptr_array_unref(sockets); + if (ret > 0) { + return ret; + } return 0; } -int rexmpp_jingle_dtls_pull_timeout_func (gnutls_transport_ptr_t p, - unsigned int ms) -{ - rexmpp_jingle_component_t *comp = p; - return rexmpp_jingle_dtls_generic_pull_timeout_func(comp->session, ms, - comp->component_id); +const char *rexmpp_ice_component_state_text(int state) { + switch (state) { + case NICE_COMPONENT_STATE_DISCONNECTED: return "disconnected"; + case NICE_COMPONENT_STATE_GATHERING: return "gathering"; + case NICE_COMPONENT_STATE_CONNECTING: return "connecting"; + case NICE_COMPONENT_STATE_CONNECTED: return "connected"; + case NICE_COMPONENT_STATE_READY: return "ready"; + case NICE_COMPONENT_STATE_FAILED: return "failed"; + case NICE_COMPONENT_STATE_LAST: return "last"; + default: return "unknown"; + } } void @@ -1083,6 +1353,9 @@ rexmpp_jingle_component_state_changed_cb (NiceAgent *agent, { rexmpp_jingle_session_t *sess = data; (void)agent; + rexmpp_log(sess->s, LOG_DEBUG, + "ICE agent component %d state for stream %d changed to %s", + component_id, stream_id, rexmpp_ice_component_state_text(state)); if (component_id < 1 || component_id > 2) { rexmpp_log(sess->s, LOG_CRIT, "Unexpected ICE component_id: %d", component_id); @@ -1101,30 +1374,13 @@ rexmpp_jingle_component_state_changed_cb (NiceAgent *agent, return; } - int active_role = rexmpp_jingle_dtls_is_active(sess, 0); - - gnutls_session_t *tls_session = &sess->component[component_id - 1].dtls_session; - gnutls_init(tls_session, - (active_role ? GNUTLS_CLIENT : GNUTLS_SERVER) | - GNUTLS_DATAGRAM | - GNUTLS_NONBLOCK); - if (! active_role) { - gnutls_certificate_server_set_request(*tls_session, GNUTLS_CERT_REQUEST); - } - gnutls_set_default_priority(*tls_session); - gnutls_credentials_set(*tls_session, GNUTLS_CRD_CERTIFICATE, - sess->s->jingle.dtls_cred); - - gnutls_transport_set_ptr(*tls_session, &sess->component[component_id - 1]); - gnutls_transport_set_push_function(*tls_session, rexmpp_jingle_dtls_push_func); - gnutls_transport_set_pull_function(*tls_session, rexmpp_jingle_dtls_pull_func); - gnutls_transport_set_pull_timeout_function(*tls_session, - rexmpp_jingle_dtls_pull_timeout_func); + rexmpp_jingle_component_t *comp = &(sess->component[component_id - 1]); + rexmpp_dtls_connect(sess->s, + comp->dtls, + comp, + rexmpp_jingle_dtls_is_active(sess, 0)); sess->component[component_id - 1].dtls_state = REXMPP_TLS_HANDSHAKE; - /* todo: use the profile/crypto-suite from <crypto/> element */ - gnutls_srtp_set_profile(*tls_session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80); - gnutls_handshake(*tls_session); - + rexmpp_tls_handshake(sess->s, comp->dtls); } else if (state == NICE_COMPONENT_STATE_FAILED) { rexmpp_log(sess->s, LOG_ERR, "ICE connection failed for Jingle session %s, ICE stream %d, component %d", @@ -1136,19 +1392,20 @@ rexmpp_jingle_component_state_changed_cb (NiceAgent *agent, void rexmpp_jingle_ice_recv_cb (NiceAgent *agent, guint stream_id, guint component_id, - guint len, gchar *buf, gpointer data) + guint len, gchar *gbuf, gpointer data) { /* Demultiplexing here for DTLS and SRTP: https://datatracker.ietf.org/doc/html/rfc5764#section-5.1.2 */ (void)agent; (void)stream_id; (void)component_id; + uint8_t *buf = (uint8_t *)gbuf; rexmpp_jingle_component_t *comp = data; if (len == 0) { rexmpp_log(comp->s, LOG_WARNING, "Received an empty ICE message"); return; } - if (127 < (uint8_t)buf[0] && (uint8_t)buf[0] < 192) { + if (127 < buf[0] && buf[0] < 192) { int err; srtp_ctx_t *srtp_in; if (comp->dtls_state == REXMPP_TLS_ACTIVE) { @@ -1162,43 +1419,178 @@ rexmpp_jingle_ice_recv_cb (NiceAgent *agent, guint stream_id, guint component_id "Received an SRTP packet while DTLS is inactive"); return; } - uint16_t port_out = comp->udp_port_out; + if (srtp_in == NULL) { + rexmpp_log(comp->s, LOG_WARNING, + "Received an SRTP packet while SRTP is not set up"); + return; + } if (component_id == 1) { - err = srtp_unprotect(srtp_in, buf, &len); + err = srtp_unprotect(srtp_in, buf, (int*)&len); if (err == srtp_err_status_auth_fail && comp->session->rtcp_mux) { /* Try to demultiplex. Maybe there's a better way to do it, but this will do for now. */ - err = srtp_unprotect_rtcp(srtp_in, buf, &len); - port_out = comp->session->component[0].udp_port_out; + err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len); } } else { - err = srtp_unprotect_rtcp(srtp_in, buf, &len); + err = srtp_unprotect_rtcp(srtp_in, buf, (int*)&len); } if (err) { rexmpp_log(comp->s, LOG_ERR, "SRT(C)P unprotect error %d on component %d", err, component_id); } else { - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - addr.sin_port = htons(port_out); - sendto(comp->udp_socket, buf, len, 0, - (struct sockaddr*)&addr, sizeof(struct sockaddr_in)); + /* TODO: apparently sometimes there is more than one RTCP packet + in the decrypted data; parse them all. */ + uint8_t version = (buf[0] & 0xc0) >> 6; + if (version == 2 && len >= 12) { + size_t i; + uint8_t payload_type = buf[1] & 0x7f; + uint8_t csrc_count = buf[0] & 0xF; + uint16_t seq_num = ((uint16_t)buf[2] << 8) | buf[3]; + int data_start_pos = 12 + csrc_count * 4; + + /* Exclude possible RTCP packets here, RFC 5761 */ + if (component_id == 1 + && (payload_type < 64 || payload_type > 80)) { + if ((seq_num > comp->session->rtp_last_seq_num) + || (comp->session->rtp_last_seq_num > 65500)) { + if ((comp->session->rtp_last_seq_num <= 65500) + && (seq_num - comp->session->rtp_last_seq_num > 1)) { + rexmpp_log(comp->s, LOG_NOTICE, + "%d RTP packets lost", + seq_num - comp->session->rtp_last_seq_num - 1); + } + struct ring_buf *playback = &(comp->session->ring_buffers.playback); + + /* Skip the RTP header. */ + if (payload_type == 0) { + /* PCMU */ + for (i = data_start_pos; i < len; i++) { + playback->buf[playback->write_pos] = rexmpp_pcmu_decode(buf[i]); + playback->write_pos++; + playback->write_pos %= PA_BUF_SIZE; + } + } else if (payload_type == 8) { + /* PCMA */ + for (i = data_start_pos; i < len; i++) { + playback->buf[playback->write_pos] = rexmpp_pcma_decode(buf[i]); + playback->write_pos++; + playback->write_pos %= PA_BUF_SIZE; + } + } +#ifdef HAVE_OPUS + else if (payload_type == comp->session->payload_type + && comp->session->codec == REXMPP_CODEC_OPUS) { + /* The same payload type as for output, which is Opus */ + opus_int16 decoded[5760 * 2]; + int decoded_len; + decoded_len = + opus_decode(comp->session->opus_dec, + (const unsigned char *)buf + data_start_pos, + len - data_start_pos, + decoded, + 5760, + 0); + int j; + for (j = 0; j < decoded_len; j++) { + playback->buf[playback->write_pos] = decoded[j]; + playback->write_pos++; + playback->write_pos %= PA_BUF_SIZE; + } + } +#endif /* HAVE_OPUS */ + else { + /* Some other payload type, possibly with a dynamic ID */ + rexmpp_xml_t *payload = + rexmpp_jingle_session_payload_by_id(comp->session, + payload_type); + const char *pl_name = rexmpp_xml_find_attr_val(payload, "name"); + rexmpp_log(comp->s, LOG_WARNING, + "Unhandled payload type %d, '%s'", + payload_type, pl_name); + } + comp->session->rtp_last_seq_num = seq_num; + } else { + rexmpp_log(comp->s, LOG_NOTICE, + "Out of order RTP packets received: %d after %d", + seq_num, comp->session->rtp_last_seq_num); + } + } else { + uint8_t packet_type = buf[1]; + unsigned int pos = 0; + unsigned int rtcp_len = (buf[2] << 8) | buf[3]; + if (packet_type == 200 && len >= 28) { + uint32_t rtcp_ssrc = + (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + uint64_t rtcp_ntp_timestamp = 0; + for (i = 8; i < 16; i++) { + rtcp_ntp_timestamp <<= 8; + rtcp_ntp_timestamp |= buf[i]; + } + uint32_t rtcp_rtp_timestamp = + (buf[16] << 24) | (buf[17] << 16) | (buf[18] << 8) | buf[19]; + uint32_t rtcp_packet_count = + (buf[20] << 24) | (buf[21] << 16) | (buf[22] << 8) | buf[23]; + uint32_t rtcp_octet_count = + (buf[24] << 24) | (buf[25] << 16) | (buf[26] << 8) | buf[27]; + rexmpp_log(comp->s, LOG_DEBUG, + "RTCP SR received: SSRC % " PRIx32 ", NTP TS %" PRIu64 + ", RTP TS %" PRIu32 ", PC %" PRIu32 ", OC %" PRIu32, + rtcp_ssrc, rtcp_ntp_timestamp, rtcp_rtp_timestamp, + rtcp_packet_count, rtcp_octet_count); + pos = 28; + } else if (packet_type == 201 && len >= 8) { + uint32_t rtcp_ssrc = + (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + rexmpp_log(comp->s, LOG_DEBUG, + "RTCP RR received: SSRC %x", + rtcp_ssrc); + pos = 8; + } else { + rexmpp_log(comp->s, LOG_DEBUG, "RTCP packet received, PT %d", + packet_type); + } + + while ((pos > 0) + && ((pos + 24) <= len) + && ((pos + 24) <= (rtcp_len + 1) * 4)) { + uint32_t rtcp_ssrc = (buf[pos] << 24) | (buf[pos + 1] << 16) + | (buf[pos + 2] << 8) | buf[pos + 3]; + uint8_t fraction_lost = buf[pos + 4]; + uint32_t packets_lost = + (buf[pos + 5] << 24) | (buf[pos + 6] << 16) | buf[pos + 7]; + uint32_t rtcp_ehsn = (buf[pos + 8] << 24) | (buf[pos + 9] << 16) + | (buf[pos + 10] << 8) | buf[pos + 11]; + uint32_t rtcp_jitter = (buf[pos + 12] << 24) | (buf[pos + 13] << 16) + | (buf[pos + 14] << 8) | buf[pos + 15]; + uint32_t rtcp_lsr = (buf[pos + 16] << 24) | (buf[pos + 17] << 16) + | (buf[pos + 18] << 8) | buf[pos + 19]; + uint32_t rtcp_dlsr = (buf[pos + 20] << 24) | (buf[pos + 21] << 16) + | (buf[pos + 22] << 8) | buf[pos + 23]; + rexmpp_log(comp->s, LOG_DEBUG, + "RTCP report block: SSRC % " PRIx32 ", lost %" PRIu32 + "%, %" PRIu8 " packets, " + "highest seq num %" PRIu32 + ", jitter %" PRIu32 ", LSR %" PRIu32 ", DLSR %" PRIu32, + rtcp_ssrc, fraction_lost, packets_lost, + rtcp_ehsn, rtcp_jitter, rtcp_lsr, rtcp_dlsr); + pos += 24; + } + } + } else { + rexmpp_log(comp->s, LOG_WARNING, + "Unhandled RT(C)P packet: version %d, length %d", + version, len); + } } } else { - if (comp->dtls_buf_len + len < DTLS_SRTP_BUF_SIZE) { - memcpy(comp->dtls_buf + comp->dtls_buf_len, buf, len); - comp->dtls_buf_len += len; - } else { - rexmpp_log(comp->s, LOG_WARNING, "Dropping a DTLS packet"); - } + rexmpp_dtls_feed(comp->s, comp->dtls, buf, len); } } int rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess) { - sess->ice_agent = nice_agent_new(g_main_loop_get_context (sess->s->jingle.gloop), + sess->ice_agent = nice_agent_new(g_main_loop_get_context (sess->s->jingle->gloop), NICE_COMPATIBILITY_RFC5245); if (sess->s->local_address != NULL) { NiceAddress *address = nice_address_new(); @@ -1223,7 +1615,7 @@ rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess) int i; for (i = 0; i < (sess->rtcp_mux ? 1 : 2); i++) { nice_agent_attach_recv(sess->ice_agent, sess->ice_stream_id, i + 1, - g_main_loop_get_context (sess->s->jingle.gloop), + g_main_loop_get_context (sess->s->jingle->gloop), rexmpp_jingle_ice_recv_cb, &sess->component[i]); } @@ -1231,29 +1623,6 @@ rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess) return 1; } -void -rexmpp_jingle_bind_sockets (rexmpp_jingle_session_t *sess, - uint16_t rtp_port_in, uint16_t rtp_port_out) -{ - sess->component[0].udp_port_in = rtp_port_in; - sess->component[0].udp_port_out = rtp_port_out; - sess->component[1].udp_port_in = rtp_port_in + 1; - sess->component[1].udp_port_out = rtp_port_out + 1; - int i; - for (i = 0; i < 2; i++) { - sess->component[i].udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - addr.sin_port = htons(sess->component[i].udp_port_in); - if (bind (sess->component[i].udp_socket, - (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) { - rexmpp_log(sess->s, LOG_ERR, "Failed to bind a UDP socket on port %u", - sess->component[i].udp_port_in); - } - } -} - void rexmpp_jingle_turn_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) { rexmpp_jingle_session_t *sess = ptr; if (result != NULL && result->data != NULL) { @@ -1306,25 +1675,25 @@ void rexmpp_jingle_stun_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *res void rexmpp_jingle_turn_cb (rexmpp_t *s, void *sess_ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)req; rexmpp_jingle_session_t *sess = sess_ptr; if (success) { /* use credentials */ - xmlNodePtr services = xmlFirstElementChild(response); + rexmpp_xml_t *services = rexmpp_xml_first_elem_child(response); if (rexmpp_xml_match(services, "urn:xmpp:extdisco:2", "services")) { - xmlNodePtr service = xmlFirstElementChild(services); + rexmpp_xml_t *service = rexmpp_xml_first_elem_child(services); while (service != NULL) { if (rexmpp_xml_match(service, "urn:xmpp:extdisco:2", "service")) { - char *type = xmlGetProp(service, "type"); - char *transport = xmlGetProp(service, "transport"); - char *host = xmlGetProp(service, "host"); - char *port = xmlGetProp(service, "port"); - char *username = xmlGetProp(service, "username"); - char *password = xmlGetProp(service, "password"); + const char *type = rexmpp_xml_find_attr_val(service, "type"); + const char *transport = rexmpp_xml_find_attr_val(service, "transport"); + const char *host = rexmpp_xml_find_attr_val(service, "host"); + const char *port = rexmpp_xml_find_attr_val(service, "port"); + const char *username = rexmpp_xml_find_attr_val(service, "username"); + const char *password = rexmpp_xml_find_attr_val(service, "password"); if (sess->stun_host == NULL && type != NULL && transport != NULL && host != NULL && port != NULL && @@ -1344,27 +1713,8 @@ void rexmpp_jingle_turn_cb (rexmpp_t *s, sess->turn_password = strdup(password); rexmpp_log(s, LOG_DEBUG, "Setting TURN server to %s:%s", host, port); } - - if (type != NULL) { - free(type); - } - if (transport != NULL) { - free(transport); - } - if (host != NULL) { - free(host); - } - if (port != NULL) { - free(port); - } - if (username != NULL) { - free(username); - } - if (password != NULL) { - free(password); - } } - service = service->next; + service = rexmpp_xml_next_elem_sibling(service); } if (sess->stun_host != NULL) { /* Resolve, then resolve STUN host, then connect. */ @@ -1392,25 +1742,24 @@ void rexmpp_jingle_turn_cb (rexmpp_t *s, void rexmpp_jingle_discover_turn_cb (rexmpp_t *s, void *sess_ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)req; - char *response_from = xmlGetProp(response, "from"); + const char *response_from = rexmpp_xml_find_attr_val(response, "from"); rexmpp_jingle_session_t *sess = sess_ptr; if (success) { - xmlNodePtr services = rexmpp_xml_new_node("services", "urn:xmpp:extdisco:2"); - xmlNewProp(services, "type", "turn"); - rexmpp_iq_new(s, "get", response_from, services, rexmpp_jingle_turn_cb, sess_ptr); + rexmpp_xml_t *services = + rexmpp_xml_new_elem("services", "urn:xmpp:extdisco:2"); + rexmpp_xml_add_attr(services, "type", "turn"); + rexmpp_iq_new(s, "get", response_from, services, + rexmpp_jingle_turn_cb, sess_ptr); } else { rexmpp_log(s, LOG_DEBUG, "No external service discovery, trying to connect without STUN/TURN"); nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id); } - if (response_from != NULL) { - free(response_from); - } } void rexmpp_jingle_discover_turn (rexmpp_t *s, rexmpp_jingle_session_t *sess) { @@ -1420,37 +1769,36 @@ void rexmpp_jingle_discover_turn (rexmpp_t *s, rexmpp_jingle_session_t *sess) { rexmpp_err_t rexmpp_jingle_call (rexmpp_t *s, - const char *jid, - uint16_t rtp_port_in, - uint16_t rtp_port_out) + const char *jid) { rexmpp_jingle_session_t *sess = - rexmpp_jingle_session_create(s, strdup(jid), rexmpp_gen_id(s), + rexmpp_jingle_session_create(s, strdup(jid), rexmpp_random_id(), REXMPP_JINGLE_SESSION_MEDIA, 1); - rexmpp_jingle_ice_agent_init(sess); - rexmpp_jingle_bind_sockets(sess, rtp_port_in, rtp_port_out); - rexmpp_jingle_discover_turn(s, sess); - return REXMPP_SUCCESS; + if (sess != NULL) { + rexmpp_jingle_ice_agent_init(sess); + rexmpp_jingle_discover_turn(s, sess); + return REXMPP_SUCCESS; + } else { + rexmpp_log(s, LOG_ERR, "Failed to create a Jingle session for a call"); + return REXMPP_E_OTHER; + } } rexmpp_err_t rexmpp_jingle_call_accept (rexmpp_t *s, - const char *sid, - uint16_t rtp_port_in, - uint16_t rtp_port_out) + const char *sid) { rexmpp_jingle_session_t *sess = rexmpp_jingle_session_by_id(s, sid); if (sess == NULL) { return REXMPP_E_OTHER; } rexmpp_jingle_ice_agent_init(sess); - rexmpp_jingle_bind_sockets(sess, rtp_port_in, rtp_port_out); - xmlNodePtr content = + rexmpp_xml_t *content = rexmpp_xml_find_child(sess->initiate, "urn:xmpp:jingle:1", "content"); - xmlNodePtr ice_udp_transport = + rexmpp_xml_t * ice_udp_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1", "transport"); @@ -1458,7 +1806,7 @@ rexmpp_jingle_call_accept (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "No ICE-UDP transport defined for session %s", sid); rexmpp_jingle_session_terminate (s, sid, - rexmpp_xml_new_node("unsupported-transports", "urn:xmpp:jingle:1"), + rexmpp_xml_new_elem("unsupported-transports", "urn:xmpp:jingle:1"), "No ICE-UDP transport defined"); return REXMPP_E_OTHER; } @@ -1466,71 +1814,74 @@ rexmpp_jingle_call_accept (rexmpp_t *s, rexmpp_jingle_discover_turn(s, sess); return REXMPP_SUCCESS; } -#else +#else /* ENABLE_CALLS */ + +ssize_t +rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size) +{ + (void)p; + (void)data; + (void)size; + return -1; +} rexmpp_err_t rexmpp_jingle_call (rexmpp_t *s, - const char *jid, - uint16_t rtp_port_in, - uint16_t rtp_port_out) + const char *jid) { (void)jid; - (void)rtp_port_in; - (void)rtp_port_out; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls"); return REXMPP_E_OTHER; } rexmpp_err_t rexmpp_jingle_call_accept (rexmpp_t *s, - const char *sid, - uint16_t rtp_port_in, - uint16_t rtp_port_out) + const char *sid) { (void)sid; - (void)rtp_port_in; - (void)rtp_port_out; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without support for media calls"); return REXMPP_E_OTHER; } -#endif +#endif /* ENABLE_CALLS */ -int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { +int rexmpp_jingle_iq (rexmpp_t *s, rexmpp_xml_t *elem) { int handled = 0; if (! s->enable_jingle) { return handled; } - xmlNodePtr jingle = rexmpp_xml_find_child(elem, "urn:xmpp:jingle:1", "jingle"); + rexmpp_xml_t *jingle = + rexmpp_xml_find_child(elem, "urn:xmpp:jingle:1", "jingle"); if (jingle != NULL) { handled = 1; - char *action = xmlGetProp(jingle, "action"); - char *sid = xmlGetProp(jingle, "sid"); - char *from_jid = xmlGetProp(elem, "from"); + const char *action = rexmpp_xml_find_attr_val(jingle, "action"); + const char *sid = rexmpp_xml_find_attr_val(jingle, "sid"); + const char *from_jid = rexmpp_xml_find_attr_val(elem, "from"); if (action != NULL && sid != NULL && from_jid != NULL) { if (strcmp(action, "session-initiate") == 0) { /* todo: could be more than one content element, handle that */ - xmlNodePtr content = + rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); if (content == NULL) { - rexmpp_iq_reply(s, elem, "error", rexmpp_xml_error("cancel", "bad-request")); + rexmpp_iq_reply(s, elem, "error", + rexmpp_xml_error("cancel", "bad-request")); } else { rexmpp_iq_reply(s, elem, "result", NULL); - xmlNodePtr file_description = + rexmpp_xml_t *file_description = rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5", "description"); - xmlNodePtr ibb_transport = + rexmpp_xml_t *ibb_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1", "transport"); - xmlNodePtr ice_udp_transport = + rexmpp_xml_t *ice_udp_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1", "transport"); - xmlNodePtr rtp_description = + rexmpp_xml_t *rtp_description = rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:rtp:1", "description"); if (file_description != NULL && ibb_transport != NULL) { - char *ibb_sid = xmlGetProp(ibb_transport, "sid"); + const char *ibb_sid = rexmpp_xml_find_attr_val(ibb_transport, "sid"); if (ibb_sid != NULL) { rexmpp_log(s, LOG_DEBUG, "Jingle session-initiate from %s, sid %s, ibb sid %s", @@ -1539,19 +1890,21 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid), REXMPP_JINGLE_SESSION_FILE, 0); if (sess != NULL) { - sess->initiate = xmlCopyNode(jingle, 1); - sess->ibb_sid = ibb_sid; + sess->initiate = rexmpp_xml_clone(jingle); + sess->ibb_sid = strdup(ibb_sid); } else { - rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("failed-transport", - "urn:xmpp:jingle:1"), - NULL); + rexmpp_jingle_session_terminate + (s, sid, + rexmpp_xml_new_elem("failed-transport", + "urn:xmpp:jingle:1"), + NULL); } } else { - rexmpp_log(s, LOG_ERR, "Jingle IBB transport doesn't have a sid attribute"); + rexmpp_log(s, LOG_ERR, + "Jingle IBB transport doesn't have a sid attribute"); rexmpp_jingle_session_terminate (s, sid, - rexmpp_xml_new_node("unsupported-transports", + rexmpp_xml_new_elem("unsupported-transports", "urn:xmpp:jingle:1"), NULL); } @@ -1562,25 +1915,28 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { rexmpp_jingle_session_t *sess = rexmpp_jingle_session_create(s, strdup(from_jid), strdup(sid), REXMPP_JINGLE_SESSION_MEDIA, 0); - sess->rtcp_mux = - (rexmpp_xml_find_child(rtp_description, - "urn:xmpp:jingle:apps:rtp:1", - "rtcp-mux") != NULL); - sess->initiate = xmlCopyNode(jingle, 1); -#endif + if (sess != NULL) { + sess->rtcp_mux = + (rexmpp_xml_find_child(rtp_description, + "urn:xmpp:jingle:apps:rtp:1", + "rtcp-mux") != NULL); + sess->initiate = rexmpp_xml_clone(jingle); + } +#endif /* ENABLE_CALLS */ } else if (file_description == NULL && rtp_description == NULL) { rexmpp_jingle_session_terminate (s, sid, - rexmpp_xml_new_node("unsupported-applications", + rexmpp_xml_new_elem("unsupported-applications", "urn:xmpp:jingle:1"), NULL); } else if (ibb_transport == NULL && ice_udp_transport == NULL) { - rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("unsupported-transports", - "urn:xmpp:jingle:1"), - NULL); + rexmpp_jingle_session_terminate + (s, sid, + rexmpp_xml_new_elem("unsupported-transports", + "urn:xmpp:jingle:1"), + NULL); } else { /* todo: some other error */ } @@ -1593,107 +1949,106 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { rexmpp_iq_reply(s, elem, "result", NULL); rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid); if (session != NULL) { - session->accept = xmlCopyNode(jingle, 1); - xmlNodePtr content = + session->accept = rexmpp_xml_clone(jingle); + rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); - xmlNodePtr file_description = + rexmpp_xml_t *file_description = rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5", "description"); - xmlNodePtr ibb_transport = + rexmpp_xml_t *ibb_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1", "transport"); if (ibb_transport != NULL && file_description != NULL) { - xmlNodePtr open = - rexmpp_xml_new_node("open", "http://jabber.org/protocol/ibb"); - xmlNewProp(open, "sid", session->ibb_sid); - xmlNewProp(open, "block-size", "4096"); - xmlNewProp(open, "stanza", "iq"); + rexmpp_xml_t *open = + rexmpp_xml_new_elem("open", "http://jabber.org/protocol/ibb"); + rexmpp_xml_add_attr(open, "sid", session->ibb_sid); + rexmpp_xml_add_attr(open, "block-size", "4096"); + rexmpp_xml_add_attr(open, "stanza", "iq"); rexmpp_iq_new(s, "set", session->jid, open, rexmpp_jingle_ibb_send_cb, strdup(sid)); } else { #ifdef ENABLE_CALLS - xmlNodePtr ice_udp_transport = + rexmpp_jingle_session_configure_audio(session); + rexmpp_jingle_run_audio(session); + rexmpp_xml_t *ice_udp_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1", "transport"); if (ice_udp_transport != NULL) { rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport); + } else { + rexmpp_log(s, LOG_WARNING, + "ICE-UDP transport is unset in session-accept"); } -#endif +#endif /* ENABLE_CALLS */ } + } else { + rexmpp_log(s, LOG_WARNING, "Jingle session %s is not found", sid); } } else if (strcmp(action, "transport-info") == 0) { rexmpp_iq_reply(s, elem, "result", NULL); rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid); if (session != NULL) { #ifdef ENABLE_CALLS - xmlNodePtr content = + rexmpp_xml_t *content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); - xmlNodePtr ice_udp_transport = + rexmpp_xml_t *ice_udp_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1", "transport"); if (ice_udp_transport != NULL) { rexmpp_jingle_ice_udp_add_remote(session, ice_udp_transport); } -#endif +#endif /* ENABLE_CALLS */ } } else { rexmpp_log(s, LOG_WARNING, "Unknown Jingle action: %s", action); - rexmpp_iq_reply(s, elem, "error", rexmpp_xml_error("cancel", "bad-request")); + rexmpp_iq_reply(s, elem, "error", + rexmpp_xml_error("cancel", "bad-request")); } } else { rexmpp_log(s, LOG_WARNING, "Received a malformed Jingle element"); - rexmpp_iq_reply(s, elem, "error", rexmpp_xml_error("cancel", "bad-request")); - } - if (action != NULL) { - free(action); - } - if (sid != NULL) { - free(sid); - } - if (from_jid != NULL) { - free(from_jid); + rexmpp_iq_reply(s, elem, "error", + rexmpp_xml_error("cancel", "bad-request")); } } /* XEP-0261: Jingle In-Band Bytestreams Transport Method */ - xmlNodePtr ibb_open = + rexmpp_xml_t *ibb_open = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "open"); if (ibb_open != NULL) { handled = 1; /* no-op, though could check sid here. */ rexmpp_iq_reply(s, elem, "result", NULL); } - xmlNodePtr ibb_close = + rexmpp_xml_t *ibb_close = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "close"); if (ibb_close != NULL) { handled = 1; rexmpp_iq_reply(s, elem, "result", NULL); - char *sid = xmlGetProp(ibb_close, "sid"); + const char *sid = rexmpp_xml_find_attr_val(ibb_close, "sid"); if (sid != NULL) { rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid); if (session != NULL) { rexmpp_jingle_session_terminate (s, session->sid, - rexmpp_xml_new_node("success", "urn:xmpp:jingle:1"), NULL); + rexmpp_xml_new_elem("success", "urn:xmpp:jingle:1"), NULL); } - free(sid); } } - xmlNodePtr ibb_data = + rexmpp_xml_t *ibb_data = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "data"); if (ibb_data != NULL) { handled = 1; - char *sid = xmlGetProp(ibb_data, "sid"); + const char *sid = rexmpp_xml_find_attr_val(ibb_data, "sid"); if (sid != NULL) { rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid); if (session != NULL && session->ibb_fh != NULL) { - char *data = NULL, *data_base64 = xmlNodeGetContent(ibb_data); + char *data = NULL; + const char *data_base64 = rexmpp_xml_text_child(ibb_data); if (data_base64 != NULL) { size_t data_len = 0; int base64_err = rexmpp_base64_from(data_base64, strlen(data_base64), - &data, &data_len); - free(data_base64); + &data, &data_len); if (base64_err != 0) { rexmpp_log(s, LOG_ERR, "Base-64 decoding failure"); } else { @@ -1706,7 +2061,6 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { } } } - free(sid); } /* todo: report errors */ rexmpp_iq_reply(s, elem, "result", NULL); @@ -1719,7 +2073,7 @@ int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { #ifdef ENABLE_CALLS gint poll_timeout; GPollFD poll_fds[10]; - GMainContext* gctx = g_main_loop_get_context(s->jingle.gloop); + GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop); if (g_main_context_acquire(gctx)) { gint poll_fds_n = g_main_context_query(gctx, G_PRIORITY_HIGH, @@ -1741,29 +2095,23 @@ int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } rexmpp_jingle_session_t *sess; - for (sess = s->jingle.sessions; sess != NULL; sess = sess->next) { + for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) { for (i = 0; i < 2; i++) { if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE && sess->component[i].dtls_state != REXMPP_TLS_CLOSED && sess->component[i].dtls_state != REXMPP_TLS_ERROR) { - GSocket *sock = - nice_agent_get_selected_socket(sess->ice_agent, - sess->ice_stream_id, - i + 1); - if (sock != NULL) { - int fd = g_socket_get_fd(sock); - g_object_unref(sock); + GPtrArray *sockets = + nice_agent_get_sockets(sess->ice_agent, + sess->ice_stream_id, i + 1); + guint i; + for (i = 0; i < sockets->len; i++) { + int fd = g_socket_get_fd(sockets->pdata[i]); FD_SET(fd, read_fds); if (fd > nfds) { nfds = fd; } } - if (sess->component[i].udp_socket != -1) { - FD_SET(sess->component[i].udp_socket, read_fds); - if (sess->component[i].udp_socket > nfds) { - nfds = sess->component[i].udp_socket; - } - } + g_ptr_array_unref(sockets); } } } @@ -1771,21 +2119,21 @@ int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { rexmpp_log(s, LOG_ERR, "Failed to acquire GMainContext in rexmpp_jingle_fds"); } -#else +#else /* ENABLE_CALLS */ (void)s; (void)read_fds; (void)write_fds; -#endif +#endif /* ENABLE_CALLS */ return (nfds + 1); } -struct timeval * rexmpp_jingle_timeout (rexmpp_t *s, - struct timeval *max_tv, - struct timeval *tv) { +struct timespec * rexmpp_jingle_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv) { #ifdef ENABLE_CALLS gint poll_timeout; GPollFD poll_fds[10]; - GMainContext* gctx = g_main_loop_get_context(s->jingle.gloop); + GMainContext* gctx = g_main_loop_get_context(s->jingle->gloop); if (g_main_context_acquire(gctx)) { g_main_context_query(gctx, G_PRIORITY_HIGH, @@ -1795,28 +2143,34 @@ struct timeval * rexmpp_jingle_timeout (rexmpp_t *s, g_main_context_release(gctx); rexmpp_jingle_session_t *sess; - for (sess = s->jingle.sessions; sess != NULL; sess = sess->next) { + for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) { int i; for (i = 0; i < 2; i++) { if (sess->component[i].dtls_state != REXMPP_TLS_INACTIVE && sess->component[i].dtls_state != REXMPP_TLS_CLOSED && sess->component[i].dtls_state != REXMPP_TLS_ERROR) { - int tms = gnutls_dtls_get_timeout(sess->component[i].dtls_session); + int tms = rexmpp_dtls_timeout(sess->s, sess->component[i].dtls); if (tms > 0 && (poll_timeout < 0 || tms < poll_timeout)) { poll_timeout = tms; } + /* Set poll timeout to at most 5 ms if there are connected + components, for timely transmission of Jingle data. */ + if (sess->component[i].dtls_state == REXMPP_TLS_ACTIVE && + (poll_timeout < 0 || poll_timeout > 5)) { + poll_timeout = 5; + } } } } if (poll_timeout >= 0) { int sec = poll_timeout / 1000; - int usec = (poll_timeout % 1000) * 1000; + int nsec = (poll_timeout % 1000) * 1000000; if (max_tv == NULL || (max_tv->tv_sec > sec || - (max_tv->tv_sec == sec && max_tv->tv_usec > usec))) { + (max_tv->tv_sec == sec && max_tv->tv_nsec > nsec))) { tv->tv_sec = sec; - tv->tv_usec = usec; + tv->tv_nsec = nsec; max_tv = tv; } } @@ -1824,10 +2178,10 @@ struct timeval * rexmpp_jingle_timeout (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Failed to acquire GMainContext in rexmpp_jingle_timeout"); } -#else +#else /* ENABLE_CALLS */ (void)s; (void)tv; -#endif +#endif /* ENABLE_CALLS */ return max_tv; } @@ -1837,232 +2191,282 @@ rexmpp_jingle_run (rexmpp_t *s, fd_set *write_fds) { (void)write_fds; + (void)read_fds; #ifdef ENABLE_CALLS rexmpp_jingle_session_t *sess; - int key_mat_size; - char key_mat[4096]; int err; - gnutls_datum_t client_key, client_salt, server_key, server_salt; - char client_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT * 2], - server_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT * 2]; - for (sess = s->jingle.sessions; sess != NULL; sess = sess->next) { + unsigned char key_mat[2 * (SRTP_AES_ICM_128_KEY_LEN_WSALT)], + client_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT], + server_sess_key[SRTP_AES_ICM_128_KEY_LEN_WSALT]; + for (sess = s->jingle->sessions; sess != NULL; sess = sess->next) { char input[4096 + SRTP_MAX_TRAILER_LEN]; int input_len; int comp_id; + for (comp_id = 0; comp_id < 2; comp_id++) { rexmpp_jingle_component_t *comp = &sess->component[comp_id]; if (comp->dtls_state == REXMPP_TLS_HANDSHAKE) { - int ret = gnutls_handshake(comp->dtls_session); - if (ret == 0) { + int ret = rexmpp_tls_handshake(s, comp->dtls); + if (ret == REXMPP_TLS_SUCCESS) { rexmpp_log(s, LOG_DEBUG, "DTLS connected for Jingle session %s, component %d", sess->sid, comp->component_id); comp->dtls_state = REXMPP_TLS_ACTIVE; - /* Verify the peer's fingerprint */ - - unsigned int cert_list_size = 0; - const gnutls_datum_t *cert_list; - cert_list = - gnutls_certificate_get_peers(comp->dtls_session, &cert_list_size); - if (cert_list_size != 1) { + rexmpp_xml_t *jingle = comp->session->initiator + ? comp->session->accept + : comp->session->initiate; + rexmpp_xml_t *fingerprint = + rexmpp_xml_find_child + (rexmpp_xml_find_child + (rexmpp_xml_find_child + (jingle, "urn:xmpp:jingle:1", "content"), + "urn:xmpp:jingle:transports:ice-udp:1", "transport"), + "urn:xmpp:jingle:apps:dtls:0", "fingerprint"); + if (fingerprint == NULL) { + /* todo: might be neater to check it upon receiving the + stanzas, instead of checking it here */ rexmpp_log(comp->s, LOG_ERR, - "Unexpected peer certificate list size: %d", - cert_list_size); + "No fingerprint in the peer's Jingle element"); rexmpp_jingle_session_terminate (s, sess->sid, - rexmpp_xml_new_node("security-error", "urn:xmpp:jingle:1"), - "Unexpected certificate list size; expected exactly 1."); + rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"), + "No fingerprint element"); + return REXMPP_E_TLS; } else { - xmlNodePtr jingle = comp->session->initiator - ? comp->session->accept - : comp->session->initiate; - xmlNodePtr fingerprint = - rexmpp_xml_find_child - (rexmpp_xml_find_child - (rexmpp_xml_find_child - (jingle, "urn:xmpp:jingle:1", "content"), - "urn:xmpp:jingle:transports:ice-udp:1", "transport"), - "urn:xmpp:jingle:apps:dtls:0", "fingerprint"); - if (fingerprint == NULL) { - /* todo: might be neater to check it upon receiving the - stanzas, instead of checking it here */ + const char *hash_str = rexmpp_xml_find_attr_val(fingerprint, "hash"); + if (hash_str == NULL) { rexmpp_log(comp->s, LOG_ERR, - "No fingerprint in the peer's Jingle element"); + "No hash attribute in the peer's fingerprint element"); rexmpp_jingle_session_terminate (s, sess->sid, - rexmpp_xml_new_node("connectivity-error", "urn:xmpp:jingle:1"), - "No fingerprint element"); + rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"), + "No hash attribute in the fingerprint element"); + return REXMPP_E_TLS; } else { - char *hash_str = xmlGetProp(fingerprint, "hash"); - if (hash_str == NULL) { - rexmpp_log(comp->s, LOG_ERR, - "No hash attribute in the peer's fingerprint element"); - rexmpp_jingle_session_terminate - (s, sess->sid, - rexmpp_xml_new_node("connectivity-error", "urn:xmpp:jingle:1"), - "No hash attribute in the fingerprint element"); - break; - } else { - gnutls_digest_algorithm_t algo = GNUTLS_DIG_UNKNOWN; - /* gnutls_digest_get_id uses different names, so - checking manually here. These are SDP options, - <https://datatracker.ietf.org/doc/html/rfc4572#page-8>. */ - if (strcmp(hash_str, "sha-1") == 0) { - algo = GNUTLS_DIG_SHA1; - } else if (strcmp(hash_str, "sha-224") == 0) { - algo = GNUTLS_DIG_SHA224; - } else if (strcmp(hash_str, "sha-256") == 0) { - algo = GNUTLS_DIG_SHA256; - } else if (strcmp(hash_str, "sha-384") == 0) { - algo = GNUTLS_DIG_SHA384; - } else if (strcmp(hash_str, "sha-512") == 0) { - algo = GNUTLS_DIG_SHA512; - } else if (strcmp(hash_str, "md5") == 0) { - algo = GNUTLS_DIG_MD5; - } - free(hash_str); - if (algo == GNUTLS_DIG_UNKNOWN) { + char fp[64], fp_str[64 * 3]; + size_t fp_size = 64; + if (rexmpp_tls_peer_fp(comp->s, comp->dtls, hash_str, + fp, fp_str, &fp_size)) + { + rexmpp_jingle_session_terminate + (s, sess->sid, + rexmpp_xml_new_elem("connectivity-error", + "urn:xmpp:jingle:1"), + "Failed to obtain the DTLS certificate fingerprint"); + return REXMPP_E_TLS; + } else { + const char *fingerprint_cont = + rexmpp_xml_text_child(fingerprint); + /* Fingerprint string should be uppercase, but + allowing any case for now, while Dino uses + lowercase. */ + int fingerprint_mismatch = strcasecmp(fingerprint_cont, fp_str); + if (fingerprint_mismatch) { rexmpp_log(comp->s, LOG_ERR, - "Unknown hash algorithm in the peer's fingerprint"); + "Peer's fingerprint mismatch: expected %s," + " calculated %s", + fingerprint_cont, fp_str); rexmpp_jingle_session_terminate (s, sess->sid, - rexmpp_xml_new_node("connectivity-error", "urn:xmpp:jingle:1"), - "Unknown hash algorithm for a DTLS certificate fingerprint"); - break; + rexmpp_xml_new_elem("security-error", "urn:xmpp:jingle:1"), + "DTLS certificate fingerprint mismatch"); + return REXMPP_E_TLS; } else { - - char fp[64], fp_str[64 * 3]; - size_t fp_size = 64; - gnutls_fingerprint(algo, cert_list, fp, &fp_size); - size_t i; - for (i = 0; i < fp_size; i++) { - snprintf(fp_str + i * 3, 4, "%02X:", fp[i] & 0xFF); + /* The fingerprint is fine, proceed to SRTP. */ + rexmpp_log(comp->s, LOG_DEBUG, + "Peer's fingerprint: %s", fp_str); + + rexmpp_tls_srtp_get_keys(s, comp->dtls, + SRTP_AES_128_KEY_LEN, SRTP_SALT_LEN, + key_mat); + /* client key */ + memcpy(client_sess_key, key_mat, SRTP_AES_128_KEY_LEN); + /* server key */ + memcpy(server_sess_key, key_mat + SRTP_AES_128_KEY_LEN, + SRTP_AES_128_KEY_LEN); + /* client salt */ + memcpy(client_sess_key + SRTP_AES_128_KEY_LEN, + key_mat + SRTP_AES_128_KEY_LEN * 2, + SRTP_SALT_LEN); + /* server salt */ + memcpy(server_sess_key + SRTP_AES_128_KEY_LEN, + key_mat + SRTP_AES_128_KEY_LEN * 2 + SRTP_SALT_LEN, + SRTP_SALT_LEN); + + int active_role = rexmpp_jingle_dtls_is_active(sess, 0); + + srtp_policy_t inbound; + memset(&inbound, 0x0, sizeof(srtp_policy_t)); + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp); + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp); + inbound.ssrc.type = ssrc_any_inbound; + inbound.key = active_role ? server_sess_key : client_sess_key; + inbound.window_size = 1024; + inbound.allow_repeat_tx = 1; + inbound.next = NULL; + err = srtp_create(&(comp->srtp_in), &inbound); + if (err) { + rexmpp_log(s, LOG_ERR, "Failed to create srtp_in"); } - fp_str[fp_size * 3 - 1] = 0; - - char *fingerprint_cont = xmlNodeGetContent(fingerprint); - /* Fingerprint string should be uppercase, but - allowing any case for now, while Dino uses - lowercase. */ - int fingerprint_mismatch = strcasecmp(fingerprint_cont, fp_str); - free(fingerprint_cont); - if (fingerprint_mismatch) { - rexmpp_log(comp->s, LOG_ERR, - "Peer's fingerprint mismatch: expected %s, calculated %s", - fingerprint_cont, fp_str); - rexmpp_jingle_session_terminate - (s, sess->sid, - rexmpp_xml_new_node("security-error", "urn:xmpp:jingle:1"), - "DTLS certificate fingerprint mismatch"); - break; - } else { - /* The fingerprint is fine, proceed to SRTP. */ - rexmpp_log(comp->s, LOG_DEBUG, "Peer's fingerprint: %s", fp_str); - - key_mat_size = - gnutls_srtp_get_keys(comp->dtls_session, key_mat, - SRTP_AES_ICM_128_KEY_LEN_WSALT * 2, - &client_key, &client_salt, - &server_key, &server_salt); - rexmpp_log(s, LOG_DEBUG, "SRTP key material size: %d", - key_mat_size); - memcpy(client_sess_key, client_key.data, - SRTP_AES_128_KEY_LEN); - memcpy(client_sess_key + SRTP_AES_128_KEY_LEN, - client_salt.data, SRTP_SALT_LEN); - - memcpy(server_sess_key, server_key.data, - SRTP_AES_128_KEY_LEN); - memcpy(server_sess_key + SRTP_AES_128_KEY_LEN, - server_salt.data, SRTP_SALT_LEN); - - int active_role = rexmpp_jingle_dtls_is_active(sess, 0); - - srtp_policy_t inbound; - memset(&inbound, 0x0, sizeof(srtp_policy_t)); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtp); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&inbound.rtcp); - inbound.ssrc.type = ssrc_any_inbound; - inbound.key = active_role ? server_sess_key : client_sess_key; - inbound.window_size = 1024; - inbound.allow_repeat_tx = 1; - inbound.next = NULL; - err = srtp_create(&(comp->srtp_in), &inbound); - if (err) { - rexmpp_log(s, LOG_ERR, "Failed to create srtp_in"); - } - srtp_policy_t outbound; - memset(&outbound, 0x0, sizeof(srtp_policy_t)); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp); - outbound.ssrc.type = ssrc_any_outbound; - outbound.key = active_role ? client_sess_key : server_sess_key; - outbound.window_size = 1024; - outbound.allow_repeat_tx = 1; - outbound.next = NULL; - err = srtp_create(&(comp->srtp_out), &outbound); - if (err) { - rexmpp_log(s, LOG_ERR, "Failed to create srtp_out"); - } + srtp_policy_t outbound; + memset(&outbound, 0x0, sizeof(srtp_policy_t)); + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtp); + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&outbound.rtcp); + outbound.ssrc.type = ssrc_any_outbound; + outbound.key = active_role ? client_sess_key : server_sess_key; + outbound.window_size = 1024; + outbound.allow_repeat_tx = 1; + outbound.next = NULL; + err = srtp_create(&(comp->srtp_out), &outbound); + if (err) { + rexmpp_log(s, LOG_ERR, "Failed to create srtp_out"); } } } } } - } else if (ret != GNUTLS_E_AGAIN) { - rexmpp_log(s, LOG_ERR, "DTLS error for session %s, component %d: %s", - sess->sid, comp->component_id, gnutls_strerror(ret)); + } else if (ret != REXMPP_TLS_E_AGAIN) { + rexmpp_log(s, LOG_ERR, "DTLS error for session %s, component %d", + sess->sid, comp->component_id); comp->dtls_state = REXMPP_TLS_ERROR; if (comp->component_id == 1) { rexmpp_jingle_session_terminate (s, sess->sid, - rexmpp_xml_new_node("connectivity-error", "urn:xmpp:jingle:1"), + rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"), "DTLS connection error"); - break; + return REXMPP_E_TLS; } } } - /* Handle outbound packets */ - srtp_ctx_t *srtp_out; + /* Check on the DTLS session, too. */ if (comp->dtls_state == REXMPP_TLS_ACTIVE) { - srtp_out = comp->srtp_out; - } else if ((comp->dtls_state == REXMPP_TLS_ERROR || comp->session->rtcp_mux) && - comp->session->component[0].dtls_state == REXMPP_TLS_ACTIVE) { - /* Try to reuse the first component's session. */ - srtp_out = comp->session->component[0].srtp_out; - } else { - break; + ssize_t received; + rexmpp_tls_err_t err = + rexmpp_tls_recv(s, comp->dtls, input, 4096, &received); + if (err != REXMPP_TLS_SUCCESS && err != REXMPP_TLS_E_AGAIN) { + rexmpp_log(s, LOG_ERR, + "Error on rexmpp_tls_recv (component id %d), " + "terminating Jingle session %s", + comp->component_id, sess->sid); + rexmpp_jingle_session_terminate + (s, sess->sid, + rexmpp_xml_new_elem("connectivity-error", "urn:xmpp:jingle:1"), + "TLS reading error"); + return REXMPP_E_TLS; + } } + } - if (FD_ISSET(comp->udp_socket, read_fds)) { - input_len = recv(comp->udp_socket, input, 4096, 0); - if (comp->component_id == 1) { - err = srtp_protect(srtp_out, input, &input_len); - } else { - err = srtp_protect_rtcp(srtp_out, input, &input_len); + /* Send captured audio frames for established sessions. */ + if ((sess->component[0].dtls_state == REXMPP_TLS_ACTIVE) + && (sess->component[0].srtp_out != NULL) + && (sess->codec != REXMPP_CODEC_UNDEFINED)) { + struct ring_buf *capture = &(sess->ring_buffers.capture); + while (capture->write_pos != capture->read_pos) { + if (sess->codec == REXMPP_CODEC_PCMU) { + for (input_len = 12; + input_len < 4096 && capture->write_pos != capture->read_pos; + input_len++) + { + input[input_len] = + rexmpp_pcmu_encode(capture->buf[capture->read_pos]); + capture->read_pos++; + capture->read_pos %= PA_BUF_SIZE; + sess->rtp_timestamp++; + } + } else if (sess->codec == REXMPP_CODEC_PCMA) { + for (input_len = 12; + input_len < 4096 && capture->write_pos != capture->read_pos; + input_len++) + { + input[input_len] = + rexmpp_pcma_encode(capture->buf[capture->read_pos]); + capture->read_pos++; + capture->read_pos %= PA_BUF_SIZE; + sess->rtp_timestamp++; + } + } +#ifdef HAVE_OPUS + else if (sess->codec == REXMPP_CODEC_OPUS) { + unsigned int samples_available; + if (capture->write_pos > capture->read_pos) { + samples_available = capture->write_pos - capture->read_pos; + } else { + samples_available = + (PA_BUF_SIZE - capture->read_pos) + capture->write_pos; + } + unsigned int frame_size = 480; + if (samples_available >= frame_size * 2) { + opus_int16 pcm[4096]; + /* Prepare a regular buffer */ + unsigned int i; + for (i = 0; + (i < frame_size * 2) && + (capture->write_pos != capture->read_pos); + i++) + { + pcm[i] = capture->buf[capture->read_pos]; + capture->read_pos++; + capture->read_pos %= PA_BUF_SIZE; + sess->rtp_timestamp++; + } + /* Encode it */ + int encoded_len; + encoded_len = opus_encode(sess->opus_enc, + pcm, + frame_size, + (unsigned char*)input + 12, + 4096); + if (encoded_len < 0) { + rexmpp_log(s, LOG_ERR, "Failed to encode an Opus frame"); + break; + } + input_len = 12 + encoded_len; + } else { + break; + } } +#endif /* HAVE_OPUS */ + + /* Setup an RTP header */ + uint32_t hl, nl; + hl = (2 << 30) /* version */ + | (0 << 29) /* padding */ + | (0 << 28) /* extension */ + | (0 << 24) /* CSRC count */ + | (0 << 23) /* marker */ + | (sess->payload_type << 16) /* paylaod type, RFC 3551 */ + | sess->rtp_seq_num; + sess->rtp_seq_num++; + nl = htonl(hl); + memcpy(input, &nl, sizeof(uint32_t)); + nl = htonl(sess->rtp_timestamp); + memcpy(input + 4, &nl, sizeof(uint32_t)); + nl = htonl(sess->rtp_ssrc); + memcpy(input + 8, &nl, sizeof(uint32_t)); + /* The RTP header is ready */ + + srtp_ctx_t *srtp_out = sess->component[0].srtp_out; + err = srtp_protect(srtp_out, input, &input_len); if (err) { - rexmpp_log(s, LOG_ERR, "SRT(C)P protect error %d\n", err); + rexmpp_log(s, LOG_ERR, "SRTP protect error %d", err); } else { nice_agent_send(sess->ice_agent, sess->ice_stream_id, - sess->rtcp_mux ? 1 : comp->component_id, + /* sess->rtcp_mux ? 1 : comp->component_id, */ + 1, input_len, input); } } - /* Check on the DTLS session too. */ - if (comp->dtls_state == REXMPP_TLS_ACTIVE) { - input_len = gnutls_record_recv(comp->dtls_session, input, 4096); - } } } - g_main_context_iteration(g_main_loop_get_context(s->jingle.gloop), 0); -#else + g_main_context_iteration(g_main_loop_get_context(s->jingle->gloop), 0); +#else /* ENABLE_CALLS */ (void)s; (void)read_fds; -#endif +#endif /* ENABLE_CALLS */ return REXMPP_SUCCESS; } diff --git a/src/rexmpp_jingle.h b/src/rexmpp_jingle.h index cd23efb..189614a 100644 --- a/src/rexmpp_jingle.h +++ b/src/rexmpp_jingle.h @@ -16,15 +16,20 @@ #ifdef ENABLE_CALLS #include <glib.h> #include <agent.h> -#include <gnutls/gnutls.h> #include <srtp2/srtp.h> -#define DTLS_SRTP_BUF_SIZE 0x4000 +#include "portaudio.h" +#ifdef HAVE_OPUS +#include <opus/opus.h> +#endif +#define PA_BUF_SIZE 0x4000 #endif #include "rexmpp.h" +#include "rexmpp_tls.h" + /** @brief Processes incoming Jingle IQs. */ -int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem); +int rexmpp_jingle_iq (rexmpp_t *s, rexmpp_xml_t *elem); /** @brief Destroys Jingle sessions. */ void rexmpp_jingle_stop (rexmpp_t *s); @@ -45,7 +50,7 @@ rexmpp_jingle_send_file (rexmpp_t *s, rexmpp_err_t rexmpp_jingle_session_terminate (rexmpp_t *s, const char *sid, - xmlNodePtr reason_node, + rexmpp_xml_t *reason_node, const char *reason_text); typedef struct rexmpp_jingle_component rexmpp_jingle_component_t; @@ -57,6 +62,13 @@ enum rexmpp_jingle_session_type { REXMPP_JINGLE_SESSION_MEDIA }; +enum rexmpp_codec { + REXMPP_CODEC_UNDEFINED, + REXMPP_CODEC_PCMU, + REXMPP_CODEC_PCMA, + REXMPP_CODEC_OPUS +}; + #ifdef ENABLE_CALLS /* A structure used for callbacks, to pass rexmpp_t, rexmpp_jingle_session_t, and the component ID. */ @@ -64,23 +76,31 @@ struct rexmpp_jingle_component { rexmpp_t *s; rexmpp_jingle_session_t *session; int component_id; - gnutls_session_t dtls_session; - char dtls_buf[DTLS_SRTP_BUF_SIZE]; + rexmpp_tls_t *dtls; enum tls_st dtls_state; - size_t dtls_buf_len; srtp_t srtp_in; srtp_t srtp_out; - uint16_t udp_port_in; - uint16_t udp_port_out; - int udp_socket; +}; + +struct ring_buf +{ + int16_t buf[PA_BUF_SIZE]; + unsigned int write_pos; + unsigned int read_pos; +}; + +struct pa_buffers +{ + struct ring_buf capture; + struct ring_buf playback; }; #endif struct rexmpp_jingle_session { char *jid; char *sid; - xmlNodePtr initiate; - xmlNodePtr accept; + rexmpp_xml_t *initiate; + rexmpp_xml_t *accept; rexmpp_jingle_session_t *next; /* Sessions are commonly passed to callbacks by external libraries, so it's convenient to have the corresponding rexmpp_t accessible @@ -107,13 +127,25 @@ struct rexmpp_jingle_session { int rtcp_mux; NiceAgent *ice_agent; int ice_stream_id; -#endif + PaStream *pa_stream; + /* The default codec and payload type for this stream. */ + enum rexmpp_codec codec; + uint8_t payload_type; + struct pa_buffers ring_buffers; + uint16_t rtp_seq_num; + uint16_t rtp_last_seq_num; + uint32_t rtp_timestamp; + uint32_t rtp_ssrc; +#ifdef HAVE_OPUS + OpusEncoder *opus_enc; + OpusDecoder *opus_dec; +#endif /* HAVE_POUS */ +#endif /* ENABLE_CALLS */ }; struct rexmpp_jingle_ctx { #ifdef ENABLE_CALLS GMainLoop* gloop; - gnutls_certificate_credentials_t dtls_cred; #endif rexmpp_jingle_session_t *sessions; }; @@ -121,20 +153,16 @@ struct rexmpp_jingle_ctx { int rexmpp_jingle_init (rexmpp_t *s); rexmpp_err_t rexmpp_jingle_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds); -struct timeval * rexmpp_jingle_timeout (rexmpp_t *s, - struct timeval *max_tv, - struct timeval *tv); +struct timespec * rexmpp_jingle_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv); int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds); rexmpp_err_t rexmpp_jingle_call (rexmpp_t *s, - const char *jid, - uint16_t rtp_port_in, - uint16_t rtp_port_out); + const char *jid); rexmpp_err_t rexmpp_jingle_call_accept (rexmpp_t *s, - const char *sid, - uint16_t rtp_port_in, - uint16_t rtp_port_out); + const char *sid); #endif diff --git a/src/rexmpp_openpgp.c b/src/rexmpp_openpgp.c index 13cee44..72fd38b 100644 --- a/src/rexmpp_openpgp.c +++ b/src/rexmpp_openpgp.c @@ -47,22 +47,24 @@ Possible future improvements: #ifdef HAVE_GPGME #include <gpgme.h> #endif -#include <libxml/tree.h> #include <gcrypt.h> +#include <errno.h> #include "rexmpp.h" +#include "rexmpp_xml.h" #include "rexmpp_openpgp.h" #include "rexmpp_jid.h" #include "rexmpp_pubsub.h" #include "rexmpp_base64.h" +#include "rexmpp_random.h" #ifdef HAVE_GPGME void rexmpp_pgp_fp_reply (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -71,33 +73,33 @@ void rexmpp_pgp_fp_reply (rexmpp_t *s, rexmpp_log(s, LOG_WARNING, "Failed to retrieve an OpenpPGP key"); return; } - xmlNodePtr pubsub = + rexmpp_xml_t *pubsub = rexmpp_xml_find_child(response, "http://jabber.org/protocol/pubsub", "pubsub"); if (pubsub == NULL) { rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: not a pubsub response"); return; } - xmlNodePtr items = - rexmpp_xml_find_child(pubsub, "http://jabber.org/protocol/pubsub", - "items"); + rexmpp_xml_t *items = + rexmpp_xml_find_child(pubsub, "http://jabber.org/protocol/pubsub", + "items"); if (items == NULL) { rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no items in pubsub element"); return; } - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub", "item"); if (item == NULL) { rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no item in items"); return; } - xmlNodePtr pubkey = + rexmpp_xml_t *pubkey = rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "pubkey"); if (pubkey == NULL) { rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no pubkey in item"); return; } - xmlNodePtr data = + rexmpp_xml_t *data = rexmpp_xml_find_child(pubkey, "urn:xmpp:openpgp:0", "data"); if (data == NULL) { rexmpp_log(s, LOG_ERR, "OpenPGP key retrieval: no data in pubkey"); @@ -106,10 +108,9 @@ void rexmpp_pgp_fp_reply (rexmpp_t *s, char *key_raw = NULL; size_t key_raw_len = 0; - char *key_base64 = xmlNodeGetContent(data); + const char *key_base64 = rexmpp_xml_text_child(data); int base64_err = rexmpp_base64_from(key_base64, strlen(key_base64), &key_raw, &key_raw_len); - free(key_base64); if (base64_err != 0) { rexmpp_log(s, LOG_ERR, "Base-64 key decoding failure"); return; @@ -140,18 +141,19 @@ void rexmpp_pgp_fp_reply (rexmpp_t *s, rexmpp_err_t rexmpp_openpgp_check_keys (rexmpp_t *s, const char *jid, - xmlNodePtr items) + rexmpp_xml_t *items) { - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event", "item"); - xmlNodePtr list = - rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list"); - xmlNodePtr metadata; - for (metadata = xmlFirstElementChild(list); + rexmpp_xml_t *list = + rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list"); + rexmpp_xml_t *metadata; + for (metadata = rexmpp_xml_first_elem_child(list); metadata != NULL; - metadata = xmlNextElementSibling(metadata)) { - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + metadata = rexmpp_xml_next_elem_sibling(metadata)) { + const char *fingerprint = + rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); gpgme_key_t key; gpgme_error_t err; err = gpgme_get_key(s->pgp_ctx, fingerprint, &key, 0); @@ -162,97 +164,95 @@ rexmpp_openpgp_check_keys (rexmpp_t *s, rexmpp_log(s, LOG_DEBUG, "Unknown OpenPGP key fingerprint for %s: %s", jid, fingerprint); - xmlNodePtr fp_req = xmlNewNode(NULL, "pubsub"); - xmlNewNs(fp_req, "http://jabber.org/protocol/pubsub", NULL); - xmlNodePtr fp_req_items = xmlNewNode(NULL, "items"); - xmlNewProp(fp_req_items, "max_items", "1"); + rexmpp_xml_t *fp_req = + rexmpp_xml_new_elem("pubsub", "http://jabber.org/protocol/pubsub"); + rexmpp_xml_t *fp_req_items = + rexmpp_xml_new_elem("items", NULL); + rexmpp_xml_add_attr(fp_req_items, "max_items", "1"); char key_node[72]; snprintf(key_node, 72, "urn:xmpp:openpgp:0:public-keys:%s", fingerprint); - xmlNewProp(fp_req_items, "node", key_node); - xmlAddChild(fp_req, fp_req_items); + rexmpp_xml_add_attr(fp_req_items, "node", key_node); + rexmpp_xml_add_child(fp_req, fp_req_items); rexmpp_iq_new(s, "get", jid, fp_req, rexmpp_pgp_fp_reply, NULL); } else if (gpg_err_code(err) != GPG_ERR_NO_ERROR) { rexmpp_log(s, LOG_WARNING, "OpenPGP error when looking for a key: %s", gpgme_strerror(err)); } - free(fingerprint); } return REXMPP_SUCCESS; } -xmlNodePtr rexmpp_published_fingerprints (rexmpp_t *s, const char *jid) { - xmlNodePtr published = +rexmpp_xml_t *rexmpp_published_fingerprints (rexmpp_t *s, const char *jid) { + rexmpp_xml_t *published = rexmpp_find_event(s, jid, "urn:xmpp:openpgp:0:public-keys", NULL); if (published == NULL) { return NULL; } - xmlNodePtr event = + rexmpp_xml_t *event = rexmpp_xml_find_child(published, "http://jabber.org/protocol/pubsub#event", "event"); - xmlNodePtr items = + rexmpp_xml_t *items = rexmpp_xml_find_child(event, "http://jabber.org/protocol/pubsub#event", "items"); - xmlNodePtr item = + rexmpp_xml_t *item = rexmpp_xml_find_child(items, "http://jabber.org/protocol/pubsub#event", "item"); - xmlNodePtr list = + rexmpp_xml_t *list = rexmpp_xml_find_child(item, "urn:xmpp:openpgp:0", "public-keys-list"); - xmlNodePtr published_fps = xmlFirstElementChild(list); + rexmpp_xml_t *published_fps = list->alt.elem.children; return published_fps; } int rexmpp_openpgp_key_is_published (rexmpp_t *s, const char *fp) { - xmlNodePtr metadata; + rexmpp_xml_t *metadata; for (metadata = rexmpp_published_fingerprints(s, s->assigned_jid.bare); metadata != NULL; - metadata = xmlNextElementSibling(metadata)) { + metadata = metadata->next) { if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) { continue; } - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); if (fingerprint == NULL) { rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); continue; } - int matches = (strcmp(fingerprint, fp) == 0); - free(fingerprint); - if (matches) { + if (strcmp(fingerprint, fp) == 0) { return 1; } } return 0; } -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_remove_key_from_list (rexmpp_t *s, const char *fp) { - xmlNodePtr fps = - xmlCopyNodeList(rexmpp_published_fingerprints(s, s->assigned_jid.bare)); - xmlNodePtr metadata, prev = NULL; + rexmpp_xml_t *fps = + rexmpp_xml_clone_list(rexmpp_published_fingerprints(s, s->assigned_jid.bare)); + rexmpp_xml_t *metadata, *prev = NULL; for (metadata = fps; metadata != NULL; - prev = metadata, metadata = xmlNextElementSibling(metadata)) { + prev = metadata, metadata = rexmpp_xml_next_elem_sibling(metadata)) { if (! rexmpp_xml_match(metadata, "urn:xmpp:openpgp:0", "pubkey-metadata")) { continue; } - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + const char *fingerprint = + rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); if (fingerprint == NULL) { rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); continue; } int matches = (strcmp(fingerprint, fp) == 0); - free(fingerprint); if (matches) { if (prev != NULL) { prev->next = metadata->next; } else { fps = metadata->next; } - xmlFreeNode(metadata); + rexmpp_xml_free(metadata); return fps; } } @@ -261,8 +261,8 @@ rexmpp_openpgp_remove_key_from_list (rexmpp_t *s, void rexmpp_pgp_key_publish_list_iq (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -275,18 +275,18 @@ void rexmpp_pgp_key_publish_list_iq (rexmpp_t *s, rexmpp_log(s, LOG_INFO, "Published an OpenpPGP key list"); } -void rexmpp_pgp_key_fp_list_upload (rexmpp_t *s, xmlNodePtr metadata) { - xmlNodePtr keylist = xmlNewNode(NULL, "public-keys-list"); - xmlNewNs(keylist, "urn:xmpp:openpgp:0", NULL); - xmlAddChild(keylist, metadata); +void rexmpp_pgp_key_fp_list_upload (rexmpp_t *s, rexmpp_xml_t *metadata) { + rexmpp_xml_t *keylist = + rexmpp_xml_new_elem("public-keys-list", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(keylist, metadata); rexmpp_pubsub_item_publish(s, NULL, "urn:xmpp:openpgp:0:public-keys", NULL, keylist, rexmpp_pgp_key_publish_list_iq, NULL); } void rexmpp_pgp_key_delete_iq (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -295,18 +295,17 @@ void rexmpp_pgp_key_delete_iq (rexmpp_t *s, rexmpp_log(s, LOG_WARNING, "Failed to delete an OpenpPGP key"); return; } - xmlNodePtr pubsub = xmlFirstElementChild(req); - xmlNodePtr publish = xmlFirstElementChild(pubsub); - char *node = xmlGetProp(publish, "node"); - char *fingerprint = node + 31; + rexmpp_xml_t *pubsub = req->alt.elem.children; + rexmpp_xml_t *publish = pubsub->alt.elem.children;; + const char *node = rexmpp_xml_find_attr_val(publish, "node"); + const char *fingerprint = node + 31; rexmpp_log(s, LOG_INFO, "Removed OpenpPGP key %s", fingerprint); - free(node); } void rexmpp_pgp_key_publish_iq (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -316,10 +315,10 @@ void rexmpp_pgp_key_publish_iq (rexmpp_t *s, return; } rexmpp_log(s, LOG_INFO, "Uploaded an OpenpPGP key"); - xmlNodePtr pubsub = xmlFirstElementChild(req); - xmlNodePtr publish = xmlFirstElementChild(pubsub); - char *node = xmlGetProp(publish, "node"); - char *fingerprint = node + 31; + rexmpp_xml_t *pubsub = req->alt.elem.children; + rexmpp_xml_t *publish = pubsub->alt.elem.children;; + const char *node = rexmpp_xml_find_attr_val(publish, "node"); + const char *fingerprint = node + 31; char time_str[42]; time_t t = time(NULL); @@ -327,22 +326,20 @@ void rexmpp_pgp_key_publish_iq (rexmpp_t *s, gmtime_r(&t, &utc_time); strftime(time_str, 42, "%FT%TZ", &utc_time); - xmlNodePtr metadata = xmlNewNode(NULL, "pubkey-metadata"); - xmlNewNs(metadata, "urn:xmpp:openpgp:0", NULL); - xmlNewProp(metadata, "date", time_str); - xmlNewProp(metadata, "v4-fingerprint", fingerprint); + rexmpp_xml_t *metadata = + rexmpp_xml_new_elem("pubkey-metadata", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(metadata, "date", time_str); + rexmpp_xml_add_attr(metadata, "v4-fingerprint", fingerprint); - free(node); - - xmlNodePtr fps = rexmpp_openpgp_remove_key_from_list(s, fingerprint); + rexmpp_xml_t *fps = rexmpp_openpgp_remove_key_from_list(s, fingerprint); if (fps != NULL) { - metadata->next = xmlCopyNodeList(fps); + metadata->next = fps; } rexmpp_pgp_key_fp_list_upload(s, metadata); } void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) { - xmlNodePtr new_fp_list = rexmpp_openpgp_remove_key_from_list(s, fp); + rexmpp_xml_t *new_fp_list = rexmpp_openpgp_remove_key_from_list(s, fp); if (new_fp_list != NULL) { rexmpp_pgp_key_fp_list_upload(s, new_fp_list); } @@ -382,14 +379,14 @@ rexmpp_err_t rexmpp_openpgp_publish_key (rexmpp_t *s, const char *fp) { key_raw = gpgme_data_release_and_get_mem(key_dh, &key_raw_len); rexmpp_base64_to(key_raw, key_raw_len, &key_base64, &key_base64_len); free(key_raw); - xmlNodePtr data = xmlNewNode(NULL, "data"); - xmlNewNs(data, "urn:xmpp:openpgp:0", NULL); - xmlNodeAddContent(data, key_base64); + rexmpp_xml_t *data = + rexmpp_xml_new_elem("data", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_text(data, key_base64); free(key_base64); - xmlNodePtr pubkey = xmlNewNode(NULL, "pubkey"); - xmlNewNs(pubkey, "urn:xmpp:openpgp:0", NULL); - xmlAddChild(pubkey, data); + rexmpp_xml_t *pubkey = + rexmpp_xml_new_elem("pubkey", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(pubkey, data); char time_str[42]; time_t t = time(NULL); @@ -421,9 +418,9 @@ int rexmpp_openpgp_fingerprint_matches (const char *f1, const char *f2) { return 1; } -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, - xmlNodePtr message, + rexmpp_xml_t *message, int *valid) { gpgme_error_t err; @@ -433,14 +430,13 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Not a message element"); return NULL; } - char *from_str = xmlGetProp(message, "from"); + const char *from_str = rexmpp_xml_find_attr_val(message, "from"); if (from_str == NULL) { rexmpp_log(s, LOG_ERR, "No 'from' attribute"); return NULL; } rexmpp_jid_parse(from_str, &from); - free(from_str); - char *to_str = xmlGetProp(message, "to"); + const char *to_str = rexmpp_xml_find_attr_val(message, "to"); if (to_str == NULL) { if (strcmp(from.bare, s->assigned_jid.bare) != 0) { rexmpp_log(s, LOG_ERR, "No 'to' attribute"); @@ -449,18 +445,16 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, rexmpp_jid_parse(from.full, &to); } else { rexmpp_jid_parse(to_str, &to); - free(to_str); } - xmlNodePtr openpgp = + rexmpp_xml_t *openpgp = rexmpp_xml_find_child(message, "urn:xmpp:openpgp:0", "openpgp"); if (openpgp == NULL) { rexmpp_log(s, LOG_ERR, "No 'openpgp' child element"); return NULL; } - char *cipher_str = xmlNodeGetContent(openpgp); - xmlNodePtr plain = + const char *cipher_str = rexmpp_xml_text_child(openpgp); + rexmpp_xml_t *plain = rexmpp_openpgp_decrypt_verify(s, cipher_str); - free(cipher_str); if (plain == NULL) { return NULL; } @@ -476,23 +470,20 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, return plain; } - xmlNodePtr child; + rexmpp_xml_t *child; int found = 0; - for (child = xmlFirstElementChild(plain); + for (child = rexmpp_xml_first_elem_child(plain); child != NULL && ! found; - child = xmlNextElementSibling(child)) + child = rexmpp_xml_next_elem_sibling(child)) { if (rexmpp_xml_match(child, "urn:xmpp:openpgp:0", "to")) { - char *to_jid = xmlGetProp(child, "jid"); + const char *to_jid = rexmpp_xml_find_attr_val(child, "jid"); if (to_jid == NULL) { rexmpp_log(s, LOG_WARNING, "Found a 'to' element without a 'jid' attribute"); } else if (strcmp(to_jid, to.bare) == 0) { found = 1; } - if (to_jid != NULL) { - free(to_jid); - } } } if (! found) { @@ -520,11 +511,11 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, } found = 0; - xmlNodePtr metadata; + rexmpp_xml_t *metadata; for (metadata = rexmpp_published_fingerprints(s, from.bare); metadata != NULL && ! found; - metadata = xmlNextElementSibling(metadata)) { - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); if (fingerprint == NULL) { rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); continue; @@ -532,7 +523,6 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, if (rexmpp_openpgp_fingerprint_matches(fingerprint, sig->fpr)) { found = 1; } - free(fingerprint); } if (! found) { rexmpp_log(s, LOG_ERR, "No %s's known key matches that of the signature", @@ -570,7 +560,7 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, return plain; } -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify (rexmpp_t *s, const char *cipher_base64) { @@ -600,7 +590,7 @@ rexmpp_openpgp_decrypt_verify (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Failed to release and get memory"); return NULL; } - xmlNodePtr elem = rexmpp_xml_parse(plain, plain_len); + rexmpp_xml_t *elem = rexmpp_xml_parse(plain, plain_len); if(elem == NULL) { rexmpp_log(s, LOG_ERR, "Failed to parse an XML document"); } @@ -615,11 +605,11 @@ void rexmpp_openpgp_add_keys (rexmpp_t *s, int *allocated) { gpgme_error_t err; - xmlNodePtr metadata; + rexmpp_xml_t *metadata; for (metadata = rexmpp_published_fingerprints(s, jid); metadata != NULL; - metadata = xmlNextElementSibling(metadata)) { - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); if (fingerprint == NULL) { rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); continue; @@ -630,7 +620,15 @@ void rexmpp_openpgp_add_keys (rexmpp_t *s, *nkeys = *nkeys + 1; if (*nkeys == *allocated) { *allocated = *allocated * 2; - *keys = realloc(*keys, sizeof(gpgme_key_t *) * *allocated); + gpgme_key_t *new_keys = + realloc(*keys, sizeof(gpgme_key_t *) * *allocated); + if (new_keys == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to reallocate the OpenPGP keys array: %s", + strerror(errno)); + continue; + } + *keys = new_keys; } } else { gpgme_key_unref((*keys)[*nkeys]); @@ -643,19 +641,18 @@ void rexmpp_openpgp_add_keys (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s", fingerprint, gpgme_strerror(err)); } - free(fingerprint); } } void rexmpp_openpgp_set_signers (rexmpp_t *s) { gpgme_error_t err; - xmlNodePtr metadata; + rexmpp_xml_t *metadata; gpgme_key_t sec_key; gpgme_signers_clear(s->pgp_ctx); for (metadata = rexmpp_published_fingerprints(s, s->initial_jid.bare); metadata != NULL; - metadata = xmlNextElementSibling(metadata)) { - char *fingerprint = xmlGetProp(metadata, "v4-fingerprint"); + metadata = metadata->next) { + const char *fingerprint = rexmpp_xml_find_attr_val(metadata, "v4-fingerprint"); if (fingerprint == NULL) { rexmpp_log(s, LOG_WARNING, "No fingerprint found in pubkey-metadata"); continue; @@ -670,12 +667,11 @@ void rexmpp_openpgp_set_signers (rexmpp_t *s) { rexmpp_log(s, LOG_ERR, "Failed to read key %s: %s", fingerprint, gpgme_strerror(err)); } - free(fingerprint); } } char *rexmpp_openpgp_payload (rexmpp_t *s, - xmlNodePtr payload, + rexmpp_xml_t *payload, const char **recipients, const char **signers, enum rexmpp_ox_mode mode) @@ -693,8 +689,8 @@ char *rexmpp_openpgp_payload (rexmpp_t *s, } else if (mode == REXMPP_OX_CRYPT) { elem_name = "crypt"; } - xmlNodePtr elem = xmlNewNode(NULL, elem_name); - xmlNewNs(elem, "urn:xmpp:openpgp:0", NULL); + rexmpp_xml_t *elem = + rexmpp_xml_new_elem(elem_name, "urn:xmpp:openpgp:0"); if (mode == REXMPP_OX_SIGN || mode == REXMPP_OX_SIGNCRYPT) { if (signers == NULL) { @@ -717,10 +713,10 @@ char *rexmpp_openpgp_payload (rexmpp_t *s, /* Add all the recipients. */ for (i = 0; recipients[i] != NULL; i++) { - xmlNodePtr to = xmlNewNode(NULL, "to"); - xmlNewNs(to, "urn:xmpp:openpgp:0", NULL); - xmlNewProp(to, "jid", recipients[i]); - xmlAddChild(elem, to); + rexmpp_xml_t *to = + rexmpp_xml_new_elem("to", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(to, "jid", recipients[i]); + rexmpp_xml_add_child(elem, to); } } @@ -730,21 +726,26 @@ char *rexmpp_openpgp_payload (rexmpp_t *s, struct tm utc_time; gmtime_r(&t, &utc_time); strftime(time_str, 42, "%FT%TZ", &utc_time); - xmlNodePtr time = xmlNewNode(NULL, "time"); - xmlNewNs(time, "urn:xmpp:openpgp:0", NULL); - xmlNewProp(time, "stamp", time_str); - xmlAddChild(elem, time); + rexmpp_xml_t *time = + rexmpp_xml_new_elem("time", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_attr(time, "stamp", time_str); + rexmpp_xml_add_child(elem, time); /* Add the payload. */ - xmlNodePtr pl = xmlNewNode(NULL, "payload"); - xmlNewNs(pl, "urn:xmpp:openpgp:0", NULL); - xmlAddChild(pl, payload); - xmlAddChild(elem, pl); + rexmpp_xml_t *pl = + rexmpp_xml_new_elem("payload", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_child(pl, payload); + rexmpp_xml_add_child(elem, pl); if (mode == REXMPP_OX_CRYPT || mode == REXMPP_OX_SIGNCRYPT) { /* Add keys for encryption. */ allocated = 8; keys = malloc(sizeof(gpgme_key_t *) * allocated); + if (keys == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for keys"); + rexmpp_xml_free(elem); + return NULL; + } keys[0] = NULL; rexmpp_openpgp_add_keys(s, s->initial_jid.bare, &keys, &nkeys, &allocated); for (i = 0; recipients[i] != NULL; i++) { @@ -753,21 +754,21 @@ char *rexmpp_openpgp_payload (rexmpp_t *s, /* A random-length random-content padding. */ char *rand_str, rand[256]; - gcry_create_nonce(rand, 1); + rexmpp_random_buf(rand, 1); size_t rand_str_len = 0, rand_len = (unsigned char)rand[0] % (255 - 16) + 16; - gcry_create_nonce(rand, rand_len); + rexmpp_random_buf(rand, rand_len); rexmpp_base64_to(rand, rand_len, &rand_str, &rand_str_len); - xmlNodePtr rpad = xmlNewNode(NULL, "rpad"); - xmlNewNs(rpad, "urn:xmpp:openpgp:0", NULL); - xmlNodeAddContent(rpad, rand_str); + rexmpp_xml_t *rpad = + rexmpp_xml_new_elem("rpad", "urn:xmpp:openpgp:0"); + rexmpp_xml_add_text(rpad, rand_str); free(rand_str); - xmlAddChild(elem, rpad); + rexmpp_xml_add_child(elem, rpad); } /* Serialize the resulting XML. */ - char *plaintext = rexmpp_xml_serialize(elem); - xmlFreeNode(elem); + char *plaintext = rexmpp_xml_serialize(elem, 0); + rexmpp_xml_free(elem); /* Encrypt, base64-encode. */ gpgme_data_t cipher_dh, plain_dh; @@ -832,7 +833,7 @@ rexmpp_err_t gpgme_not_supported(rexmpp_t *s) { rexmpp_err_t rexmpp_openpgp_check_keys (rexmpp_t *s, const char *jid, - xmlNodePtr items) { + rexmpp_xml_t *items) { (void)jid; (void)items; return gpgme_not_supported(s); @@ -848,7 +849,7 @@ void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp) { gpgme_not_supported(s); } -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify (rexmpp_t *s, const char *cipher_base64) { (void)cipher_base64; @@ -856,9 +857,9 @@ rexmpp_openpgp_decrypt_verify (rexmpp_t *s, return NULL; } -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, - xmlNodePtr message, + rexmpp_xml_t *message, int *valid) { (void)message; (void)valid; @@ -867,14 +868,14 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, } char *rexmpp_openpgp_payload (rexmpp_t *s, - xmlNodePtr payload, + rexmpp_xml_t *payload, const char **recipients, const char **signers, enum rexmpp_ox_mode mode) { (void)recipients; (void)signers; (void)mode; - xmlFreeNode(payload); + rexmpp_xml_free(payload); gpgme_not_supported(s); return NULL; } diff --git a/src/rexmpp_openpgp.h b/src/rexmpp_openpgp.h index 7470347..2132930 100644 --- a/src/rexmpp_openpgp.h +++ b/src/rexmpp_openpgp.h @@ -29,7 +29,7 @@ enum rexmpp_ox_mode { rexmpp_err_t rexmpp_openpgp_check_keys (rexmpp_t *s, const char *jid, - xmlNodePtr items); + rexmpp_xml_t *items); /** @brief Publishes a key via PEP/pubsub. @@ -52,7 +52,7 @@ void rexmpp_openpgp_retract_key (rexmpp_t *s, const char *fp); @param[in] cipher_base64 An OpenPGP ciphertext. @returns A plaintext message body. */ -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify (rexmpp_t *s, const char *cipher_base64); @@ -65,9 +65,9 @@ rexmpp_openpgp_decrypt_verify (rexmpp_t *s, valid. @returns A decrypted message body. */ -xmlNodePtr +rexmpp_xml_t * rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, - xmlNodePtr message, + rexmpp_xml_t *message, int *valid); /** @@ -83,7 +83,7 @@ rexmpp_openpgp_decrypt_verify_message (rexmpp_t *s, @returns An encoded <openpgp> payload. */ char *rexmpp_openpgp_payload (rexmpp_t *s, - xmlNodePtr payload, + rexmpp_xml_t *payload, const char **recipients, const char **signers, enum rexmpp_ox_mode mode); diff --git a/src/rexmpp_pubsub.c b/src/rexmpp_pubsub.c index 145a352..58cb060 100644 --- a/src/rexmpp_pubsub.c +++ b/src/rexmpp_pubsub.c @@ -7,25 +7,22 @@ */ #include "rexmpp.h" +#include "rexmpp_xml.h" void rexmpp_pubsub_iq (rexmpp_t *s, const char *iq_type, const char *pubsub_namespace, const char *service_jid, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t callback, void *cb_data) { - xmlNodePtr pubsub = xmlNewNode(NULL, "pubsub"); if (pubsub_namespace == NULL) { - xmlNewNs(pubsub, "http://jabber.org/protocol/pubsub", NULL); - } else { - xmlNewNs(pubsub, pubsub_namespace, NULL); + pubsub_namespace = "http://jabber.org/protocol/pubsub"; } - - xmlAddChild(pubsub, payload); - + rexmpp_xml_t *pubsub = rexmpp_xml_new_elem("pubsub", pubsub_namespace); + rexmpp_xml_add_child(pubsub, payload); rexmpp_iq_new(s, iq_type, service_jid, pubsub, callback, cb_data); } @@ -34,21 +31,21 @@ rexmpp_pubsub_item_publish (rexmpp_t *s, const char *service_jid, const char *node, const char *item_id, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t callback, void *cb_data) { - xmlNodePtr item = xmlNewNode(NULL, "item"); - xmlNewNs(item, "http://jabber.org/protocol/pubsub", NULL); + rexmpp_xml_t *item = + rexmpp_xml_new_elem("item", "http://jabber.org/protocol/pubsub"); if (item_id != NULL) { - xmlNewProp(item, "id", item_id); + rexmpp_xml_add_attr(item, "id", item_id); } - xmlAddChild(item, payload); + rexmpp_xml_add_child(item, payload); - xmlNodePtr publish = xmlNewNode(NULL, "publish"); - xmlNewNs(publish, "http://jabber.org/protocol/pubsub", NULL); - xmlNewProp(publish, "node", node); - xmlAddChild(publish, item); + rexmpp_xml_t *publish = + rexmpp_xml_new_elem("publish", "http://jabber.org/protocol/pubsub"); + rexmpp_xml_add_attr(publish, "node", node); + rexmpp_xml_add_child(publish, item); rexmpp_pubsub_iq(s, "set", NULL, service_jid, publish, callback, cb_data); } @@ -61,16 +58,16 @@ rexmpp_pubsub_item_retract (rexmpp_t *s, rexmpp_iq_callback_t callback, void *cb_data) { - xmlNodePtr item = xmlNewNode(NULL, "item"); - xmlNewNs(item, "http://jabber.org/protocol/pubsub", NULL); + rexmpp_xml_t *item = + rexmpp_xml_new_elem("item", "http://jabber.org/protocol/pubsub"); if (item_id != NULL) { - xmlNewProp(item, "id", item_id); + rexmpp_xml_add_attr(item, "id", item_id); } - xmlNodePtr retract = xmlNewNode(NULL, "retract"); - xmlNewNs(retract, "http://jabber.org/protocol/pubsub", NULL); - xmlNewProp(retract, "node", node); - xmlAddChild(retract, item); + rexmpp_xml_t *retract = + rexmpp_xml_new_elem("retract", "http://jabber.org/protocol/pubsub"); + rexmpp_xml_add_attr(retract, "node", node); + rexmpp_xml_add_child(retract, item); rexmpp_pubsub_iq(s, "set", NULL, service_jid, retract, callback, cb_data); } @@ -82,9 +79,9 @@ rexmpp_pubsub_node_delete (rexmpp_t *s, rexmpp_iq_callback_t callback, void *cb_data) { - xmlNodePtr delete = xmlNewNode(NULL, "delete"); - xmlNewNs(delete, "http://jabber.org/protocol/pubsub#owner", NULL); - xmlNewProp(delete, "node", node); + rexmpp_xml_t *delete = + rexmpp_xml_new_elem("delete", "http://jabber.org/protocol/pubsub#owner"); + rexmpp_xml_add_attr(delete, "node", node); rexmpp_pubsub_iq(s, "set", "http://jabber.org/protocol/pubsub#owner", service_jid, delete, callback, cb_data); diff --git a/src/rexmpp_pubsub.h b/src/rexmpp_pubsub.h index b5a7c3e..86675d3 100644 --- a/src/rexmpp_pubsub.h +++ b/src/rexmpp_pubsub.h @@ -11,7 +11,7 @@ rexmpp_pubsub_iq (rexmpp_t *s, const char *iq_type, const char *pubsub_namespace, const char *service_jid, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t callback, void *cb_data); @@ -20,7 +20,7 @@ rexmpp_pubsub_item_publish (rexmpp_t *s, const char *service_jid, const char *node, const char *item_id, - xmlNodePtr payload, + rexmpp_xml_t *payload, rexmpp_iq_callback_t callback, void *cb_data); diff --git a/src/rexmpp_random.c b/src/rexmpp_random.c new file mode 100644 index 0000000..11e2b73 --- /dev/null +++ b/src/rexmpp_random.c @@ -0,0 +1,34 @@ +/** + @file rexmpp_random.c + @brief Random generation + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#include "config.h" +#include "rexmpp_base64.h" + +#ifdef HAVE_GCRYPT +#include <gcrypt.h> +#else +#define _GNU_SOURCE +#include <stdlib.h> +#endif + + +void rexmpp_random_buf (void *buf, size_t len) { +#ifdef HAVE_GCRYPT + gcry_create_nonce(buf, len); +#else + arc4random_buf(buf, len); +#endif +} + +char *rexmpp_random_id () { + char buf_raw[18], *buf_base64 = NULL; + size_t buf_base64_len = 0; + rexmpp_random_buf(buf_raw, 18); + rexmpp_base64_to(buf_raw, 18, &buf_base64, &buf_base64_len); + return buf_base64; +} diff --git a/src/rexmpp_random.h b/src/rexmpp_random.h new file mode 100644 index 0000000..b0f1978 --- /dev/null +++ b/src/rexmpp_random.h @@ -0,0 +1,29 @@ +/** + @file rexmpp_random.h + @brief Random generation + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_RANDOM_H +#define REXMPP_RANDOM_H + +/** + @brief Fills a buffer with cryptographically-secure random data. + @param[out] buf A buffer to write into. + @param[in] len The number of bytes to fill. + + Uses arc4random_buf or gcry_create_nonce, depending on what is + available. +*/ +void rexmpp_random_buf (void *buf, size_t len); + +/** + @brief Generates a random ASCII identifier. + @returns A null-terminated string, which must be freed by the + caller. +*/ +char *rexmpp_random_id (); + +#endif diff --git a/src/rexmpp_random.rs b/src/rexmpp_random.rs new file mode 100644 index 0000000..dbabc71 --- /dev/null +++ b/src/rexmpp_random.rs @@ -0,0 +1,5 @@ +use std::os::raw::{c_char}; + +extern { + pub fn rexmpp_random_id () -> *mut c_char; +} diff --git a/src/rexmpp_roster.c b/src/rexmpp_roster.c index 63a52b9..3deb5b7 100644 --- a/src/rexmpp_roster.c +++ b/src/rexmpp_roster.c @@ -7,24 +7,24 @@ */ #include "rexmpp.h" +#include "rexmpp_xml.h" #include <syslog.h> #include <string.h> -#include <libxml/tree.h> -#include <libxml/xmlsave.h> +#include <stdlib.h> -xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s, - const char *jid, - xmlNodePtr *prev_item) +rexmpp_xml_t * +rexmpp_roster_find_item (rexmpp_t *s, + const char *jid, + rexmpp_xml_t **prev_item) { - xmlNodePtr prev = NULL, cur = s->roster_items; + rexmpp_xml_t *prev = NULL, *cur = s->roster_items; while (cur != NULL) { - char *cur_jid = xmlGetProp(cur, "jid"); + const char *cur_jid = rexmpp_xml_find_attr_val(cur, "jid"); if (cur_jid == NULL) { rexmpp_log(s, LOG_ALERT, "No jid found in a roster item."); return NULL; } int match = (strcmp(cur_jid, jid) == 0); - free(cur_jid); if (match) { if (prev_item != NULL) { *prev_item = prev; @@ -32,75 +32,75 @@ xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s, return cur; } prev = cur; - cur = cur->next; + cur = rexmpp_xml_next_elem_sibling(cur); } return NULL; } -rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, xmlNodePtr item) { +rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, rexmpp_xml_t *item) { rexmpp_err_t ret = REXMPP_SUCCESS; if (! rexmpp_xml_match(item, "jabber:iq:roster", "item")) { rexmpp_log(s, LOG_ERR, "No roster item."); return REXMPP_E_PARAM; } - char *subscription = xmlGetProp(item, "subscription"); - char *jid = xmlGetProp(item, "jid"); + const char *subscription = rexmpp_xml_find_attr_val(item, "subscription"); + const char *jid = rexmpp_xml_find_attr_val(item, "jid"); if (subscription != NULL && strcmp(subscription, "remove") == 0) { /* Delete the item. */ - xmlNodePtr prev, cur; + rexmpp_xml_t *prev, *cur; cur = rexmpp_roster_find_item(s, jid, &prev); if (cur != NULL) { if (prev != NULL) { - prev->next = cur->next; + prev->next = rexmpp_xml_next_elem_sibling(cur); } else { - s->roster_items = cur->next; + s->roster_items = rexmpp_xml_next_elem_sibling(cur); } - xmlFreeNode(cur); + rexmpp_xml_free(cur); } else { ret = REXMPP_E_ROSTER_ITEM_NOT_FOUND; } } else { /* Add or modify the item. */ - xmlNodePtr cur, prev; + rexmpp_xml_t *cur, *prev; cur = rexmpp_roster_find_item(s, jid, &prev); /* Remove the item if it was in the roster before. */ if (cur != NULL) { if (prev != NULL) { - prev->next = cur->next; + prev->next = rexmpp_xml_next_elem_sibling(cur); } else { - s->roster_items = cur->next; + s->roster_items = rexmpp_xml_next_elem_sibling(cur); } - xmlFreeNode(cur); + rexmpp_xml_free(cur); } /* Add the new item. */ - xmlNodePtr new_item = xmlCopyNode(item, 1); + rexmpp_xml_t *new_item = rexmpp_xml_clone(item); new_item->next = s->roster_items; s->roster_items = new_item; } - free(jid); - if (subscription != NULL) { - free(subscription); - } if (s->roster_modify_cb != NULL) { s->roster_modify_cb(s, item); } return ret; } -void rexmpp_roster_set (rexmpp_t *s, xmlNodePtr query) { +void rexmpp_roster_set (rexmpp_t *s, rexmpp_xml_t *query) { if (s->roster_items != NULL) { - xmlFreeNodeList(s->roster_items); + rexmpp_xml_free_list(s->roster_items); } if (s->roster_ver != NULL) { free(s->roster_ver); } - s->roster_ver = xmlGetProp(query, "ver"); - s->roster_items = xmlCopyNodeList(xmlFirstElementChild(query)); + const char *roster_ver = rexmpp_xml_find_attr_val(query, "ver"); + s->roster_ver = NULL; + if (roster_ver != NULL) { + s->roster_ver = strdup(roster_ver); + } + s->roster_items = rexmpp_xml_clone_list(rexmpp_xml_first_elem_child(query)); if (s->roster_modify_cb != NULL) { - xmlNodePtr item; - for (item = xmlFirstElementChild(query); + rexmpp_xml_t *item; + for (item = rexmpp_xml_first_elem_child(query); item != NULL; - item = xmlNextElementSibling(item)) + item = rexmpp_xml_next_elem_sibling(item)) { s->roster_modify_cb(s, item); } @@ -112,10 +112,11 @@ void rexmpp_roster_cache_read (rexmpp_t *s) { rexmpp_log(s, LOG_WARNING, "No roster cache file path is set."); return; } - xmlDocPtr doc = xmlReadFile(s->roster_cache_file, "utf-8", XML_PARSE_NONET); - xmlNodePtr query = xmlDocGetRootElement(doc); - rexmpp_roster_set(s, query); - xmlFreeDoc(doc); + rexmpp_xml_t *query = rexmpp_xml_read_file(s->roster_cache_file); + if (query != NULL) { + rexmpp_roster_set(s, query); + rexmpp_xml_free(query); + } } void rexmpp_roster_cache_write (rexmpp_t *s) { @@ -123,24 +124,22 @@ void rexmpp_roster_cache_write (rexmpp_t *s) { rexmpp_log(s, LOG_WARNING, "No roster cache file path is set."); return; } - xmlDocPtr doc = xmlNewDoc("1.0"); - xmlNodePtr query = xmlNewDocNode(doc, NULL, "query", NULL); - xmlDocSetRootElement(doc, query); - xmlNewNs(query, "jabber:iq:roster", NULL); + + rexmpp_xml_t *query = rexmpp_xml_new_elem("query", "jabber:iq:roster"); if (s->roster_ver != NULL) { - xmlNewProp(query, "ver", s->roster_ver); + rexmpp_xml_add_attr(query, "ver", s->roster_ver); } if (s->roster_items != NULL) { - xmlAddChild(query, xmlDocCopyNodeList(doc, s->roster_items)); + rexmpp_xml_add_child(query, rexmpp_xml_clone_list(s->roster_items)); } - xmlSaveFileEnc(s->roster_cache_file, doc, "utf-8"); - xmlFreeDoc(doc); + rexmpp_xml_write_file(s->roster_cache_file, query); + rexmpp_xml_free(query); } void rexmpp_iq_roster_get (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success) { (void)ptr; @@ -149,8 +148,9 @@ void rexmpp_iq_roster_get (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Roster loading failed."); return; } - xmlNodePtr query = xmlFirstElementChild(response); - if (! rexmpp_xml_match(query, "jabber:iq:roster", "query")) { + rexmpp_xml_t *query = + rexmpp_xml_find_child(response, "jabber:iq:roster", "query"); + if (query == NULL) { rexmpp_log(s, LOG_DEBUG, "No roster query in reply."); return; } diff --git a/src/rexmpp_roster.h b/src/rexmpp_roster.h index 1fa9183..f613a7b 100644 --- a/src/rexmpp_roster.h +++ b/src/rexmpp_roster.h @@ -7,15 +7,15 @@ */ -xmlNodePtr rexmpp_roster_find_item (rexmpp_t *s, - const char *jid, - xmlNodePtr *prev_item); -rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, xmlNodePtr item); -void rexmpp_roster_set (rexmpp_t *s, xmlNodePtr query); +rexmpp_xml_t *rexmpp_roster_find_item (rexmpp_t *s, + const char *jid, + rexmpp_xml_t **prev_item); +rexmpp_err_t rexmpp_modify_roster (rexmpp_t *s, rexmpp_xml_t *item); +void rexmpp_roster_set (rexmpp_t *s, rexmpp_xml_t *query); void rexmpp_roster_cache_read (rexmpp_t *s); void rexmpp_roster_cache_write (rexmpp_t *s); void rexmpp_iq_roster_get (rexmpp_t *s, void *ptr, - xmlNodePtr req, - xmlNodePtr response, + rexmpp_xml_t *req, + rexmpp_xml_t *response, int success); diff --git a/src/rexmpp_rust.rs b/src/rexmpp_rust.rs new file mode 100644 index 0000000..ba80598 --- /dev/null +++ b/src/rexmpp_rust.rs @@ -0,0 +1,8 @@ +mod rexmpp_jid; +mod rexmpp_xml; +mod rexmpp_xml_parser; +mod rexmpp_dns; +mod rexmpp_tcp; +mod rexmpp_socks; +mod rexmpp_random; +mod rexmpp; diff --git a/src/rexmpp_sasl.c b/src/rexmpp_sasl.c index 7dd16ba..20c4ba0 100644 --- a/src/rexmpp_sasl.c +++ b/src/rexmpp_sasl.c @@ -8,6 +8,7 @@ */ #include <syslog.h> +#include <stdlib.h> #include "config.h" #include "rexmpp.h" @@ -31,28 +32,33 @@ int rexmpp_sasl_cb (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop) { } int rexmpp_sasl_ctx_init (rexmpp_t *s) { - int err = gsasl_init(&(s->sasl.ctx)); + s->sasl = malloc(sizeof(struct rexmpp_sasl_ctx)); + int err = gsasl_init(&(s->sasl->ctx)); if (err != GSASL_OK) { rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s", gsasl_strerror(err)); return -1; } - gsasl_callback_hook_set(s->sasl.ctx, s); - gsasl_callback_set(s->sasl.ctx, rexmpp_sasl_cb); + gsasl_callback_hook_set(s->sasl->ctx, s); + gsasl_callback_set(s->sasl->ctx, rexmpp_sasl_cb); return 0; } void rexmpp_sasl_ctx_deinit (rexmpp_t *s) { - gsasl_done(s->sasl.ctx); + gsasl_done(s->sasl->ctx); + if (s->sasl != NULL) { + free(s->sasl); + s->sasl = NULL; + } } void rexmpp_sasl_ctx_cleanup (rexmpp_t *s) { - gsasl_finish(s->sasl.session); - s->sasl.session = NULL; + gsasl_finish(s->sasl->session); + s->sasl->session = NULL; } int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) { - int sasl_err = gsasl_encode (s->sasl.session, in, in_len, out, out_len); + int sasl_err = gsasl_encode (s->sasl->session, in, in_len, out, out_len); if (sasl_err != GSASL_OK) { rexmpp_log(s, LOG_ERR, "SASL encoding error: %s", gsasl_strerror(sasl_err)); return -1; @@ -61,7 +67,7 @@ int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, } int rexmpp_sasl_decode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) { - int sasl_err = gsasl_decode(s->sasl.session, in, in_len, out, out_len); + int sasl_err = gsasl_decode(s->sasl->session, in, in_len, out, out_len); if (sasl_err != GSASL_OK) { rexmpp_log(s, LOG_ERR, "SASL decoding error: %s", gsasl_strerror(sasl_err)); return -1; @@ -70,15 +76,15 @@ int rexmpp_sasl_decode (rexmpp_t *s, const char *in, size_t in_len, char **out, } const char *rexmpp_sasl_suggest_mechanism (rexmpp_t *s, const char *mech_list) { - return gsasl_client_suggest_mechanism(s->sasl.ctx, mech_list); + return gsasl_client_suggest_mechanism(s->sasl->ctx, mech_list); } void rexmpp_sasl_property_set (rexmpp_t *s, rexmpp_sasl_property prop, const char *data) { - gsasl_property_set (s->sasl.session, (Gsasl_property)prop, data); + gsasl_property_set (s->sasl->session, (Gsasl_property)prop, data); } int rexmpp_sasl_start (rexmpp_t *s, const char *mech) { - int sasl_err = gsasl_client_start(s->sasl.ctx, mech, &(s->sasl.session)); + int 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)); @@ -88,7 +94,7 @@ int rexmpp_sasl_start (rexmpp_t *s, const char *mech) { } int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) { - int sasl_err = gsasl_step64 (s->sasl.session, b64_in, b64_out); + int sasl_err = gsasl_step64 (s->sasl->session, b64_in, b64_out); if (sasl_err != GSASL_OK) { if (sasl_err == GSASL_NEEDS_MORE) { rexmpp_log(s, LOG_DEBUG, "SASL needs more data"); @@ -106,26 +112,30 @@ int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) { #include <memory.h> int rexmpp_sasl_ctx_init (rexmpp_t *s) { - s->sasl.mech = REXMPP_SASL_MECH_UNKNOWN; - s->sasl.authid = NULL; - s->sasl.password = NULL; + s->sasl = malloc(sizeof(struct rexmpp_sasl_ctx)); + s->sasl->mech = REXMPP_SASL_MECH_UNKNOWN; + s->sasl->authid = NULL; + s->sasl->password = NULL; return 0; } void rexmpp_sasl_ctx_cleanup (rexmpp_t *s) { - s->sasl.mech = REXMPP_SASL_MECH_UNKNOWN; - if (s->sasl.authid != NULL) { - free(s->sasl.authid); - s->sasl.authid = NULL; + s->sasl->mech = REXMPP_SASL_MECH_UNKNOWN; + if (s->sasl->authid != NULL) { + free(s->sasl->authid); + s->sasl->authid = NULL; } - if (s->sasl.password != NULL) { - free(s->sasl.password); - s->sasl.password = NULL; + if (s->sasl->password != NULL) { + free(s->sasl->password); + s->sasl->password = NULL; } } void rexmpp_sasl_ctx_deinit (rexmpp_t *s) { - (void)s; + if (s->sasl != NULL) { + free(s->sasl); + s->sasl = NULL; + } } int rexmpp_sasl_encode (rexmpp_t *s, const char *in, size_t in_len, char **out, size_t *out_len) { @@ -186,7 +196,7 @@ const char *rexmpp_sasl_suggest_mechanism (rexmpp_t *s, const char *mech_list) { int rexmpp_sasl_start (rexmpp_t *s, const char *mech) { rexmpp_sasl_mechanism m = rexmpp_sasl_mech_read(mech); if (m != REXMPP_SASL_MECH_UNKNOWN) { - s->sasl.mech = m; + s->sasl->mech = m; return 0; } return -1; @@ -194,15 +204,15 @@ int rexmpp_sasl_start (rexmpp_t *s, const char *mech) { const char *rexmpp_sasl_get_prop (rexmpp_t *s, rexmpp_sasl_property prop) { if (prop == REXMPP_SASL_PROP_AUTHID) { - if (s->sasl.authid == NULL) { + if (s->sasl->authid == NULL) { s->sasl_property_cb(s, prop); } - return s->sasl.authid; + return s->sasl->authid; } else if (prop == REXMPP_SASL_PROP_PASSWORD) { - if (s->sasl.password == NULL) { + if (s->sasl->password == NULL) { s->sasl_property_cb(s, prop); } - return s->sasl.password; + return s->sasl->password; } return NULL; } @@ -210,7 +220,7 @@ const char *rexmpp_sasl_get_prop (rexmpp_t *s, rexmpp_sasl_property prop) { int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) { (void)s; (void)b64_in; - if (s->sasl.mech == REXMPP_SASL_MECH_PLAIN) { + if (s->sasl->mech == REXMPP_SASL_MECH_PLAIN) { /* RFC 4616 */ const char *authid = rexmpp_sasl_get_prop(s, REXMPP_SASL_PROP_AUTHID); const char *password = rexmpp_sasl_get_prop(s, REXMPP_SASL_PROP_PASSWORD); @@ -226,7 +236,7 @@ int rexmpp_sasl_step64 (rexmpp_t *s, const char *b64_in, char **b64_out) { free(auth); return 0; } - } else if (s->sasl.mech == REXMPP_SASL_MECH_EXTERNAL) { + } else if (s->sasl->mech == REXMPP_SASL_MECH_EXTERNAL) { *b64_out = strdup(""); return 0; } @@ -237,15 +247,15 @@ void rexmpp_sasl_property_set (rexmpp_t *s, rexmpp_sasl_property prop, const cha (void)s; (void)data; if (prop == REXMPP_SASL_PROP_AUTHID) { - if (s->sasl.authid != NULL) { - free(s->sasl.authid); + if (s->sasl->authid != NULL) { + free(s->sasl->authid); } - s->sasl.authid = strdup(data); + s->sasl->authid = strdup(data); } else if (prop == REXMPP_SASL_PROP_PASSWORD) { - if (s->sasl.password != NULL) { - free(s->sasl.password); + if (s->sasl->password != NULL) { + free(s->sasl->password); } - s->sasl.password = strdup(data); + s->sasl->password = strdup(data); } } diff --git a/src/rexmpp_socks.rs b/src/rexmpp_socks.rs new file mode 100644 index 0000000..1828de1 --- /dev/null +++ b/src/rexmpp_socks.rs @@ -0,0 +1,180 @@ +// For rustc and libstd-rust version 1.48 + +use std::os::raw::{c_int, c_char}; +use std::ffi::CStr; +use std::net::TcpStream; +use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::io::Write; +use std::io::Read; +use std::io::ErrorKind; +use std::convert::TryFrom; + +const REXMPP_SOCKS_BUF_LEN: usize = 300; + +#[derive(PartialEq)] +#[repr(C)] +enum SocksIOState { + Writing, + Reading +} + +#[derive(PartialEq)] +#[repr(C)] +enum SocksStage { + Auth, + Cmd, + Done +} + +#[derive(PartialEq)] +#[repr(C)] +enum SocksErr { + Connected, + EAgain, + ETCP, + EReply, + EVersion, + ESocks, + EHost +} + +#[repr(C)] +pub struct RexmppSocks { + fd: c_int, + host: *const c_char, + port: u16, + stage: SocksStage, + io_state: SocksIOState, + socks_error: c_int, + buf: [u8; REXMPP_SOCKS_BUF_LEN], + buf_len: usize, + buf_sent: usize +} + +#[no_mangle] +extern "C" fn rexmpp_socks_proceed (s : &mut RexmppSocks) -> SocksErr { + if s.io_state == SocksIOState::Writing { + let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) }; + let ret = stream.write(&s.buf[s.buf_sent .. s.buf_len]); + // Make sure the connection is not closed by TcpStream. + TcpStream::into_raw_fd(stream); + match ret { + Ok(sent) => { + s.buf_sent += sent; + if s.buf_len == s.buf_sent { + s.buf_len = 0; + s.io_state = SocksIOState::Reading; + } + } + Err(error) => match error.kind() { + ErrorKind::WouldBlock => return SocksErr::EAgain, + _ => return SocksErr::ETCP + } + } + } else if s.io_state == SocksIOState::Reading { + let mut stream : TcpStream = unsafe { TcpStream::from_raw_fd(s.fd) }; + let ret = stream.read(&mut s.buf[s.buf_len ..]); + // Make sure the connection is not closed by TcpStream. + TcpStream::into_raw_fd(stream); + match ret { + Ok(received) => { + s.buf_len += received; + if s.buf[0] != 5 { + return SocksErr::EVersion; + } + if s.buf_len >= 2 { + s.socks_error = s.buf[1].into(); + } + if s.stage == SocksStage::Auth { + if s.buf_len > 2 { + return SocksErr::EReply; + } + if s.buf_len == 2 { + if s.socks_error != 0 { + return SocksErr::ESocks; + } + s.buf[0] = 5; // SOCKS version 5 + s.buf[1] = 1; // Connect + s.buf[2] = 0; // Reserved + s.buf[3] = 3; // Domain name (todo: IP addresses) + let host_cstr : &CStr = + unsafe { CStr::from_ptr(s.host) }; + let host_len = host_cstr.to_bytes().len(); + match u8::try_from(host_len) { + Ok(u) => { s.buf[4] = u } + Err(_) => return SocksErr::EHost + } + s.buf[5 .. 5 + host_len]. + copy_from_slice(&host_cstr.to_bytes()); + s.buf[5 + host_len .. 7 + host_len]. + copy_from_slice(&s.port.to_be_bytes()); + s.buf_len = 7 + host_len; + s.buf_sent = 0; + s.stage = SocksStage::Cmd; + s.io_state = SocksIOState::Writing; + return rexmpp_socks_proceed(s); + } + } else if s.stage == SocksStage::Cmd { + if s.buf_len >= 5 { + let mut full_len : usize = 6; + match s.buf[3] { + // IPv4 + 1 => full_len += 4, + // Domain name + 3 => full_len += usize::from(s.buf[4]) + 1, + // IPv6 + 4 => full_len += 16, + _ => return SocksErr::EReply + } + if s.buf_len > full_len { + return SocksErr::EReply; + } + if s.buf_len == full_len { + if s.socks_error != 0 { + return SocksErr::ESocks; + } + // We are done + s.stage = SocksStage::Done; + return SocksErr::Connected; + } + } + } + } + Err(error) => match error.kind() { + ErrorKind::WouldBlock => return SocksErr::EAgain, + _ => return SocksErr::ETCP + } + } + } + return SocksErr::EAgain +} + +#[no_mangle] +extern "C" fn rexmpp_socks_init ( + s : &mut RexmppSocks, + fd: c_int, + host: *const c_char, + port: u16 +) + -> SocksErr +{ + s.fd = fd; + s.host = host; + s.port = port; + s.socks_error = 0; + + let host_cstr : &CStr = unsafe { CStr::from_ptr(host) }; + if host_cstr.to_bytes().len() > 255 { + return SocksErr::EHost; + } + + // Request authentication + s.stage = SocksStage::Auth; + s.io_state = SocksIOState::Writing; + s.buf[0] = 5; // SOCKS version 5 + s.buf[1] = 1; // 1 supported method + s.buf[2] = 0; // No authentication required + s.buf_len = 3; + s.buf_sent = 0; + return rexmpp_socks_proceed(s); +} diff --git a/src/rexmpp_tcp.c b/src/rexmpp_tcp.c index 3022182..c6a53a5 100644 --- a/src/rexmpp_tcp.c +++ b/src/rexmpp_tcp.c @@ -50,10 +50,10 @@ void rexmpp_tcp_dns_a_cb (rexmpp_t *s, conn->addr_cur_v4 = -1; if (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) { /* Wait for 50 ms for IPv6. */ - gettimeofday(&(conn->next_connection_time), NULL); - conn->next_connection_time.tv_usec += REXMPP_TCP_IPV6_DELAY_MS * 1000; - if (conn->next_connection_time.tv_usec >= 1000000) { - conn->next_connection_time.tv_usec -= 1000000; + clock_gettime(CLOCK_MONOTONIC, &(conn->next_connection_time)); + conn->next_connection_time.tv_nsec += REXMPP_TCP_IPV6_DELAY_MS * 1000000; + if (conn->next_connection_time.tv_nsec >= 1000000000) { + conn->next_connection_time.tv_nsec -= 1000000000; conn->next_connection_time.tv_sec++; } } @@ -99,6 +99,21 @@ rexmpp_tcp_connected (rexmpp_tcp_conn_t *conn, int fd) { return REXMPP_CONN_DONE; } +int rexmpp_tcp_socket(rexmpp_t *s, int domain) { + int sock = socket(domain, SOCK_STREAM, 0); + + /* Make it non-blocking */ + int flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + + /* Call the socket creation callback, if provided */ + if (s->socket_cb != NULL) { + s->socket_cb(s, sock); + } + + return sock; +} + rexmpp_tcp_conn_error_t rexmpp_tcp_conn_init (rexmpp_t *s, rexmpp_tcp_conn_t *conn, @@ -116,20 +131,16 @@ rexmpp_tcp_conn_init (rexmpp_t *s, conn->fd = -1; conn->dns_secure = 0; conn->next_connection_time.tv_sec = 0; - conn->next_connection_time.tv_usec = 0; + conn->next_connection_time.tv_nsec = 0; conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE; conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE; struct sockaddr_in addr_v4; - int flags; - if (inet_pton(AF_INET, host, &addr_v4)) { + if (inet_pton(AF_INET, host, &(addr_v4.sin_addr))) { addr_v4.sin_family = AF_INET; addr_v4.sin_port = htons(port); - conn->sockets[conn->connection_attempts] = - socket(AF_INET, SOCK_STREAM, 0); - flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); - fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, AF_INET); if (connect(conn->sockets[conn->connection_attempts], (struct sockaddr*)&addr_v4, sizeof(addr_v4))) { @@ -144,15 +155,12 @@ rexmpp_tcp_conn_init (rexmpp_t *s, return REXMPP_CONN_IN_PROGRESS; } struct sockaddr_in6 addr_v6; - if (inet_pton(AF_INET6, host, &addr_v6)) { + if (inet_pton(AF_INET6, host, &(addr_v6.sin6_addr))) { addr_v6.sin6_family = AF_INET6; addr_v6.sin6_port = htons(port); addr_v6.sin6_flowinfo = 0; addr_v6.sin6_scope_id = 0; - conn->sockets[conn->connection_attempts] = - socket(AF_INET6, SOCK_STREAM, 0); - flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); - fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, AF_INET6); if (connect(conn->sockets[conn->connection_attempts], (struct sockaddr*)&addr_v6, sizeof(addr_v6))) { @@ -201,7 +209,7 @@ rexmpp_tcp_conn_proceed (rexmpp_t *s, fd_set *write_fds) { (void)read_fds; /* Not checking any read FDs at the moment. */ - struct timeval now; + struct timespec now; int i; /* Check for successful connections. */ @@ -241,10 +249,10 @@ rexmpp_tcp_conn_proceed (rexmpp_t *s, if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS && (rexmpp_tcp_conn_ipv4_available(conn) || rexmpp_tcp_conn_ipv6_available(conn))) { - gettimeofday(&now, NULL); + clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec > conn->next_connection_time.tv_sec || (now.tv_sec == conn->next_connection_time.tv_sec && - now.tv_usec >= conn->next_connection_time.tv_usec)) { + now.tv_nsec >= conn->next_connection_time.tv_nsec)) { /* Time to attempt a new connection. */ int use_ipv6 = 0; if (rexmpp_tcp_conn_ipv4_available(conn) && @@ -295,16 +303,13 @@ rexmpp_tcp_conn_proceed (rexmpp_t *s, addrlen = sizeof(addr_v4); } - conn->sockets[conn->connection_attempts] = - socket(domain, SOCK_STREAM, 0); - int flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0); - fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK); + conn->sockets[conn->connection_attempts] = rexmpp_tcp_socket(s, domain); if (connect(conn->sockets[conn->connection_attempts], addr, addrlen)) { if (errno == EINPROGRESS) { - gettimeofday(&(conn->next_connection_time), NULL); - conn->next_connection_time.tv_usec += REXMPP_TCP_CONN_DELAY_MS * 1000; - if (conn->next_connection_time.tv_usec >= 1000000) { - conn->next_connection_time.tv_usec -= 1000000; + clock_gettime(CLOCK_MONOTONIC, &(conn->next_connection_time)); + conn->next_connection_time.tv_nsec += REXMPP_TCP_CONN_DELAY_MS * 1000000; + if (conn->next_connection_time.tv_nsec >= 1000000000) { + conn->next_connection_time.tv_nsec -= 1000000000; conn->next_connection_time.tv_sec++; } conn->connection_attempts++; @@ -333,14 +338,14 @@ rexmpp_tcp_conn_proceed (rexmpp_t *s, } } - gettimeofday(&now, NULL); + clock_gettime(CLOCK_MONOTONIC, &now); if (active_connections || conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING || conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING || (conn->next_connection_time.tv_sec > now.tv_sec || (conn->next_connection_time.tv_sec == now.tv_sec && - conn->next_connection_time.tv_usec > now.tv_usec))) { + conn->next_connection_time.tv_nsec > now.tv_nsec))) { return REXMPP_CONN_IN_PROGRESS; } else { return REXMPP_CONN_FAILURE; @@ -368,13 +373,13 @@ int rexmpp_tcp_conn_fds (rexmpp_t *s, return max_fd; } -struct timeval *rexmpp_tcp_conn_timeout (rexmpp_t *s, - rexmpp_tcp_conn_t *conn, - struct timeval *max_tv, - struct timeval *tv) +struct timespec *rexmpp_tcp_conn_timeout (rexmpp_t *s, + rexmpp_tcp_conn_t *conn, + struct timespec *max_tv, + struct timespec *tv) { - struct timeval now; - struct timeval *ret = max_tv; + struct timespec now; + struct timespec *ret = max_tv; if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING || conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) { ret = rexmpp_dns_timeout(s, max_tv, tv); @@ -383,20 +388,20 @@ struct timeval *rexmpp_tcp_conn_timeout (rexmpp_t *s, conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS || (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_INACTIVE && conn->resolution_v6 == REXMPP_CONN_RESOLUTION_INACTIVE)) { - gettimeofday(&now, NULL); + clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec < conn->next_connection_time.tv_sec || (now.tv_sec == conn->next_connection_time.tv_sec && - now.tv_usec <= conn->next_connection_time.tv_usec)) { + now.tv_nsec <= conn->next_connection_time.tv_nsec)) { if (ret == NULL || ret->tv_sec > conn->next_connection_time.tv_sec - now.tv_sec || (ret->tv_sec == conn->next_connection_time.tv_sec - now.tv_sec && - ret->tv_usec > conn->next_connection_time.tv_usec - now.tv_usec)) { + ret->tv_nsec > conn->next_connection_time.tv_nsec - now.tv_nsec)) { ret = tv; tv->tv_sec = conn->next_connection_time.tv_sec - now.tv_sec; - if (conn->next_connection_time.tv_usec > now.tv_usec) { - tv->tv_usec = conn->next_connection_time.tv_usec - now.tv_usec; + if (conn->next_connection_time.tv_nsec > now.tv_nsec) { + tv->tv_nsec = conn->next_connection_time.tv_nsec - now.tv_nsec; } else { - tv->tv_usec = conn->next_connection_time.tv_usec + 1000000 - now.tv_usec; + tv->tv_nsec = conn->next_connection_time.tv_nsec + 1000000000 - now.tv_nsec; tv->tv_sec--; } } diff --git a/src/rexmpp_tcp.h b/src/rexmpp_tcp.h index 5a296cc..8ee32a0 100644 --- a/src/rexmpp_tcp.h +++ b/src/rexmpp_tcp.h @@ -20,6 +20,7 @@ #define REXMPP_TCP_H #include <sys/time.h> +#include <stdbool.h> #include "rexmpp.h" #include "rexmpp_dns.h" @@ -98,13 +99,13 @@ struct rexmpp_tcp_connection { /** @brief The number of connection attempts so far. */ int connection_attempts; - /** @brief Next scheduled connection time. */ - struct timeval next_connection_time; + /** @brief Next scheduled connection time (monotonic). */ + struct timespec next_connection_time; /** @brief File descriptor of a connected socket. */ int fd; /** @brief Whether the A or AAAA records used to establish the final connection were verified with DNSSEC. */ - int dns_secure; + bool dns_secure; }; /** @@ -175,13 +176,13 @@ int rexmpp_tcp_conn_fds (rexmpp_t *s, @param[in] s ::rexmpp @param[in] conn An active connection structure. @param[in] max_tv An existing maximum timeout. - @param[out] tv A timeval structure to store a new timeout in. + @param[out] tv A timespec structure to store a new timeout in. @returns A pointer to either max_tv or tv, depending on which one is smaller. */ -struct timeval *rexmpp_tcp_conn_timeout (rexmpp_t *s, - rexmpp_tcp_conn_t *conn, - struct timeval *max_tv, - struct timeval *tv); +struct timespec *rexmpp_tcp_conn_timeout (rexmpp_t *s, + rexmpp_tcp_conn_t *conn, + struct timespec *max_tv, + struct timespec *tv); #endif diff --git a/src/rexmpp_tcp.rs b/src/rexmpp_tcp.rs new file mode 100644 index 0000000..56c204c --- /dev/null +++ b/src/rexmpp_tcp.rs @@ -0,0 +1,469 @@ +use std::os::raw::{c_int, c_char}; +use libc::*; +use std::ptr::{null_mut,null}; +use std::mem; +use errno::{errno}; + +use super::{rexmpp_dns, rexmpp}; + + +#[link(name = "libc")] +extern { + fn inet_pton (af: c_int, src: *const c_char, dst: *mut c_void) -> c_int; +} + + +const REXMPP_TCP_MAX_CONNECTION_ATTEMPTS: usize = 20; +const REXMPP_TCP_IPV6_DELAY_MS: i64 = 50; +const REXMPP_TCP_CONN_DELAY_MS: i64 = 250; + +#[derive(PartialEq, Copy, Clone)] +#[repr(C)] +pub enum ResolutionStatus { + Inactive, + Waiting, + Success, + Failure +} + +#[derive(PartialEq, Copy, Clone)] +#[repr(C)] +pub enum ConnectionError { + Done, + ResolverError, + InProgress, + Failure, + Error +} + +#[repr(C)] +pub struct RexmppTCPConnection { + pub host: *const c_char, + pub port: u16, + pub resolution_v4: ResolutionStatus, + pub resolver_status_v4: c_int, + pub resolved_v4: *mut rexmpp_dns::RexmppDNSResult, + pub addr_cur_v4: c_int, + pub resolution_v6: ResolutionStatus, + pub resolver_status_v6: c_int, + pub resolved_v6: *mut rexmpp_dns::RexmppDNSResult, + pub addr_cur_v6: c_int, + pub sockets: [c_int; REXMPP_TCP_MAX_CONNECTION_ATTEMPTS], + pub connection_attempts: c_int, + pub next_connection_time: timespec, + pub fd: c_int, + pub dns_secure: bool +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_dns_aaaa_cb (_s: *mut rexmpp::Rexmpp, + ptr: *mut c_void, + result: *mut rexmpp_dns::RexmppDNSResult) + -> () { + let conn = ptr as *mut RexmppTCPConnection; + (*conn).resolved_v6 = result; + if result != null_mut() { + (*conn).resolution_v6 = ResolutionStatus::Success; + (*conn).addr_cur_v6 = -1; + } else { + (*conn).resolution_v6 = ResolutionStatus::Failure; + } +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_dns_a_cb (_s: *mut rexmpp::Rexmpp, + ptr: *mut c_void, + result: *mut rexmpp_dns::RexmppDNSResult) + -> () { + let conn = ptr as *mut RexmppTCPConnection; + (*conn).resolved_v4 = result; + if result != null_mut() { + (*conn).resolution_v4 = ResolutionStatus::Success; + (*conn).addr_cur_v4 = -1; + if (*conn).resolution_v6 == ResolutionStatus::Waiting { + // Wait a bit (usually 50 ms) for IPv6 + clock_gettime(CLOCK_MONOTONIC, &mut (*conn).next_connection_time); + (*conn).next_connection_time.tv_nsec += REXMPP_TCP_IPV6_DELAY_MS * 1000000; + if (*conn).next_connection_time.tv_nsec >= 1000000000 { + (*conn).next_connection_time.tv_nsec -= 1000000000; + (*conn).next_connection_time.tv_sec += 1; + } + } + } else { + (*conn).resolution_v4 = ResolutionStatus::Failure; + } +} + + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_cleanup (conn: *mut RexmppTCPConnection) -> () { + for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) { + if (*conn).sockets[i] != -1 && (*conn).sockets[i] != (*conn).fd { + close((*conn).sockets[i]); + (*conn).sockets[i] = -1; + } + } + if (*conn).resolution_v4 != ResolutionStatus::Inactive { + (*conn).resolution_v4 = ResolutionStatus::Inactive; + (*conn).resolution_v6 = ResolutionStatus::Inactive; + } + if (*conn).resolved_v4 != null_mut() { + rexmpp_dns::rexmpp_dns_result_free((*conn).resolved_v4); + (*conn).resolved_v4 = null_mut(); + } + if (*conn).resolved_v6 != null_mut() { + rexmpp_dns::rexmpp_dns_result_free((*conn).resolved_v6); + (*conn).resolved_v6 = null_mut(); + } +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_connected (conn: *mut RexmppTCPConnection, fd: c_int) + -> ConnectionError { + let mut sa_ptr = mem::MaybeUninit::<sockaddr>::uninit(); + let mut sa_len : socklen_t = mem::size_of::<sockaddr>() as u32; + getsockname(fd, sa_ptr.as_mut_ptr(), &mut sa_len); + let sa = sa_ptr.assume_init(); + if sa.sa_family == (AF_INET as u16) + && (*conn).resolved_v4 != null_mut() { + (*conn).dns_secure = (*(*conn).resolved_v4).secure; + } + else if sa.sa_family == (AF_INET6 as u16) + && (*conn).resolved_v6 != null_mut() { + (*conn).dns_secure = (*(*conn).resolved_v6).secure; + } + (*conn).fd = fd; + rexmpp_tcp_cleanup(conn); + return ConnectionError::Done; +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_socket (s: *mut rexmpp::Rexmpp, domain: c_int) -> c_int { + let sock: c_int = socket(domain, SOCK_STREAM, 0); + + // Make it non-blocking + let flags: c_int = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flags | O_NONBLOCK); + + // Call the socket creation callback, if provided + ((*s).socket_cb).map(|cb| cb(s, sock)); + + return sock; +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_init (s: *mut rexmpp::Rexmpp, + conn: *mut RexmppTCPConnection, + host: *const c_char, + port: u16) + -> ConnectionError { + for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) { + (*conn).sockets[i] = -1; + } + (*conn).connection_attempts = 0; + (*conn).port = port; + (*conn).resolved_v4 = null_mut(); + (*conn).resolved_v6 = null_mut(); + (*conn).fd = -1; + (*conn).dns_secure = false; + (*conn).next_connection_time.tv_sec = 0; + (*conn).next_connection_time.tv_nsec = 0; + + (*conn).resolution_v4 = ResolutionStatus::Inactive; + (*conn).resolution_v6 = ResolutionStatus::Inactive; + + let mut addr_v4 = mem::MaybeUninit::<sockaddr_in>::uninit(); + if inet_pton(AF_INET, host, + &mut ((*addr_v4.as_mut_ptr()).sin_addr) + as *mut in_addr as *mut c_void) == 1 { + (*addr_v4.as_mut_ptr()).sin_family = AF_INET as u16; + (*addr_v4.as_mut_ptr()).sin_port = port.to_be(); + (*conn).sockets[(*conn).connection_attempts as usize] = + rexmpp_tcp_socket(s, AF_INET); + if connect((*conn).sockets[(*conn).connection_attempts as usize], + addr_v4.as_mut_ptr() as *mut sockaddr, + mem::size_of::<sockaddr_in>() as u32) != 0 { + if errno().0 != EINPROGRESS { + return ConnectionError::Error; + } + } else { + return rexmpp_tcp_connected(conn, + (*conn).sockets[(*conn).connection_attempts as usize]); + } + (*conn).connection_attempts += 1; + return ConnectionError::InProgress; + } + + let mut addr_v6 = mem::MaybeUninit::<sockaddr_in6>::uninit(); + if inet_pton(AF_INET6, host, + &mut ((*addr_v6.as_mut_ptr()).sin6_addr) + as *mut in6_addr as *mut c_void) == 1 { + (*addr_v6.as_mut_ptr()).sin6_family = AF_INET as u16; + (*addr_v6.as_mut_ptr()).sin6_port = port.to_be(); + (*addr_v6.as_mut_ptr()).sin6_flowinfo = 0; + (*addr_v6.as_mut_ptr()).sin6_scope_id = 0; + (*conn).sockets[(*conn).connection_attempts as usize] = + rexmpp_tcp_socket(s, AF_INET6); + if connect((*conn).sockets[(*conn).connection_attempts as usize], + addr_v6.as_mut_ptr() as *mut sockaddr, + mem::size_of::<sockaddr_in6>() as u32) != 0 { + if errno().0 != EINPROGRESS { + return ConnectionError::Error; + } + } else { + return rexmpp_tcp_connected(conn, + (*conn).sockets[(*conn).connection_attempts as usize]); + } + (*conn).connection_attempts += 1; + return ConnectionError::InProgress; + } + (*conn).resolution_v4 = ResolutionStatus::Waiting; + (*conn).resolution_v6 = ResolutionStatus::Waiting; + + rexmpp_dns::rexmpp_dns_resolve(s, host, 28, 1, + conn as *mut c_void, + rexmpp_tcp_dns_aaaa_cb); + rexmpp_dns::rexmpp_dns_resolve(s, host, 1, 1, + conn as *mut c_void, + rexmpp_tcp_dns_a_cb); + return ConnectionError::InProgress; +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_finish (conn: *mut RexmppTCPConnection) -> c_int { + rexmpp_tcp_cleanup(conn); + return (*conn).fd; +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_ipv4_available (conn: *mut RexmppTCPConnection) -> bool { + (*conn).resolution_v4 == ResolutionStatus::Success + && (*conn).resolved_v4 != null_mut() + && *(*(*conn).resolved_v4).data + .offset(((*conn).addr_cur_v4 + 1) as isize) != null_mut() +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_ipv6_available (conn: *mut RexmppTCPConnection) -> bool { + (*conn).resolution_v6 == ResolutionStatus::Success + && (*conn).resolved_v6 != null_mut() + && *(*(*conn).resolved_v6).data + .offset(((*conn).addr_cur_v6 + 1) as isize) != null_mut() +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_proceed (s: *mut rexmpp::Rexmpp, + conn: *mut RexmppTCPConnection, + read_fds: *mut fd_set, + write_fds: *mut fd_set) -> ConnectionError { + // Check for successful connections. + for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) { + if (*conn).sockets[i] != -1 && FD_ISSET((*conn).sockets[i], write_fds) { + let mut err: c_int = 0; + let mut err_len: socklen_t = mem::size_of::<c_int>() as u32; + if getsockopt((*conn).sockets[i], SOL_SOCKET, SO_ERROR, + &mut err as *mut c_int as *mut c_void, + &mut err_len) < 0 { + return ConnectionError::Error; + } else { + if err == 0 { + return rexmpp_tcp_connected(conn, (*conn).sockets[i]); + } else if err != EINPROGRESS { + close((*conn).sockets[i]); + (*conn).sockets[i] = -1; + } + } + } + } + + // Name resolution + if (*conn).resolution_v4 == ResolutionStatus::Waiting + || (*conn).resolution_v6 == ResolutionStatus::Waiting { + rexmpp_dns::rexmpp_dns_process(s, read_fds, write_fds); + } + if (*conn).resolution_v4 == ResolutionStatus::Failure + && (*conn).resolution_v6 == ResolutionStatus::Failure { + // Failed to resolve anything + return ConnectionError::Failure; + } + + // New connections + let mut repeat: bool; + let mut now = mem::MaybeUninit::<timespec>::uninit(); + let now_ptr = now.as_mut_ptr(); + loop { + repeat = false; + if (*conn).connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS as i32 + && (rexmpp_tcp_conn_ipv4_available(conn) + || rexmpp_tcp_conn_ipv6_available(conn)) { + clock_gettime(CLOCK_MONOTONIC, now_ptr); + if (*now_ptr).tv_sec > (*conn).next_connection_time.tv_sec + || ((*now_ptr).tv_sec == (*conn).next_connection_time.tv_sec + && (*now_ptr).tv_nsec >= (*conn).next_connection_time.tv_nsec) { + // Time to attempt a new connection + let mut use_ipv6 = false; + if rexmpp_tcp_conn_ipv4_available(conn) && + rexmpp_tcp_conn_ipv6_available(conn) { + if (*conn).addr_cur_v4 >= (*conn).addr_cur_v6 { + use_ipv6 = true; + } + } else if rexmpp_tcp_conn_ipv6_available(conn) { + use_ipv6 = true; + } + + let addr: *mut sockaddr; + let addrlen: socklen_t; + let domain: c_int; + if use_ipv6 { + let mut addr_v6: sockaddr_in6 = mem::zeroed(); + (*conn).addr_cur_v6 += 1; + let len = (mem::size_of::<in6_addr>() as i32) + .min(*(*(*conn).resolved_v6).len.offset((*conn).addr_cur_v6 as isize)); + memcpy(&mut addr_v6.sin6_addr as *mut in6_addr as *mut c_void, + *(*(*conn).resolved_v6).data.offset((*conn).addr_cur_v6 as isize), + len as usize); + addr_v6.sin6_family = AF_INET6 as u16; + addr_v6.sin6_port = (*conn).port.to_be(); + addr_v6.sin6_flowinfo = 0; + addr_v6.sin6_scope_id = 0; + domain = AF_INET6; + addr = &mut addr_v6 as *mut sockaddr_in6 as *mut sockaddr; + addrlen = mem::size_of::<sockaddr_in6>() as u32; + } else { + let mut addr_v4: sockaddr_in = mem::zeroed(); + (*conn).addr_cur_v4 += 1; + let len = (mem::size_of::<in_addr>() as i32) + .min(*(*(*conn).resolved_v4).len.offset((*conn).addr_cur_v4 as isize)); + memcpy(&mut addr_v4.sin_addr as *mut in_addr as *mut c_void, + *(*(*conn).resolved_v4).data.offset((*conn).addr_cur_v4 as isize), + len as usize); + addr_v4.sin_family = AF_INET as u16; + addr_v4.sin_port = (*conn).port.to_be(); + domain = AF_INET; + addr = &mut addr_v4 as *mut sockaddr_in as *mut sockaddr; + addrlen = mem::size_of::<sockaddr_in>() as u32; + } + (*conn).sockets[(*conn).connection_attempts as usize] = + rexmpp_tcp_socket(s, domain); + if connect((*conn).sockets[(*conn).connection_attempts as usize], + addr, addrlen) != 0 { + if errno().0 == EINPROGRESS { + clock_gettime(CLOCK_MONOTONIC, &mut (*conn).next_connection_time); + (*conn).next_connection_time.tv_nsec += + REXMPP_TCP_CONN_DELAY_MS * 1000000; + if (*conn).next_connection_time.tv_nsec >= 1000000000 { + (*conn).next_connection_time.tv_nsec -= 1000000000; + (*conn).next_connection_time.tv_sec += 1; + } + (*conn).connection_attempts += 1; + } else { + close((*conn).sockets[(*conn).connection_attempts as usize]); + (*conn).sockets[(*conn).connection_attempts as usize] = -1; + if (*conn).connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS as i32 + && (rexmpp_tcp_conn_ipv4_available(conn) || + rexmpp_tcp_conn_ipv6_available(conn)) { + repeat = true; + } + } + } else { + return rexmpp_tcp_connected(conn, + (*conn).sockets[(*conn).connection_attempts as usize]); + } + } + } + if ! repeat { + break; + } + } + + let mut active_connections = false; + for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) { + if (*conn).sockets[i] != -1 { + active_connections = true; + break; + } + } + + clock_gettime(CLOCK_MONOTONIC, now_ptr); + + if active_connections + || (*conn).resolution_v4 == ResolutionStatus::Waiting + || (*conn).resolution_v6 == ResolutionStatus::Waiting + || ((*conn).next_connection_time.tv_sec > (*now_ptr).tv_sec + || ((*conn).next_connection_time.tv_sec == (*now_ptr).tv_sec + && (*conn).next_connection_time.tv_nsec > (*now_ptr).tv_nsec)) { + ConnectionError::InProgress + } else { + ConnectionError::Failure + } +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_fds (s: *mut rexmpp::Rexmpp, + conn: *mut RexmppTCPConnection, + read_fds: *mut fd_set, + write_fds: *mut fd_set) -> c_int { + let mut max_fd: c_int = 0; + if (*conn).resolution_v4 == ResolutionStatus::Waiting + || (*conn).resolution_v6 == ResolutionStatus::Waiting { + max_fd = rexmpp_dns::rexmpp_dns_fds(s, read_fds, write_fds); + } + for i in 0..=(REXMPP_TCP_MAX_CONNECTION_ATTEMPTS - 1) { + if (*conn).sockets[i] != -1 { + FD_SET((*conn).sockets[i], write_fds); + if max_fd < (*conn).sockets[i] + 1 { + max_fd = (*conn).sockets[i] + 1; + } + } + } + max_fd +} + +#[no_mangle] +unsafe extern "C" +fn rexmpp_tcp_conn_timeout (s: *mut rexmpp::Rexmpp, + conn: *mut RexmppTCPConnection, + max_tv: *mut timespec, + tv: *mut timespec) -> *mut timespec { + let mut now: timespec = mem::zeroed(); + let mut ret: *mut timespec = max_tv; + if (*conn).resolution_v4 == ResolutionStatus::Waiting + || (*conn).resolution_v6 == ResolutionStatus::Waiting { + ret = rexmpp_dns::rexmpp_dns_timeout(s, max_tv, tv); + } + if (*conn).resolution_v4 == ResolutionStatus::Success + || (*conn).resolution_v6 == ResolutionStatus::Success + || ((*conn).resolution_v4 == ResolutionStatus::Inactive + && (*conn).resolution_v4 == ResolutionStatus::Inactive) { + clock_gettime(CLOCK_MONOTONIC, &mut now); + if now.tv_sec < (*conn).next_connection_time.tv_sec + || (now.tv_sec == (*conn).next_connection_time.tv_sec + && now.tv_nsec <= (*conn).next_connection_time.tv_nsec) { + if ret == null_mut() + || (*ret).tv_sec > (*conn).next_connection_time.tv_sec - now.tv_sec + || ((*ret).tv_sec == (*conn).next_connection_time.tv_sec - now.tv_sec + && (*ret).tv_nsec > (*conn).next_connection_time.tv_nsec - now.tv_sec) { + ret = tv; + (*tv).tv_sec = (*conn).next_connection_time.tv_sec - now.tv_sec; + if (*conn).next_connection_time.tv_nsec > now.tv_nsec { + (*tv).tv_nsec = (*conn).next_connection_time.tv_nsec - now.tv_nsec; + } else { + (*tv).tv_nsec = (*conn).next_connection_time.tv_nsec + 1000000000 - now.tv_nsec; + (*tv).tv_sec -= 1; + } + } + } + } + ret +} diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c index 7919556..29bbc96 100644 --- a/src/rexmpp_tls.c +++ b/src/rexmpp_tls.c @@ -8,6 +8,7 @@ #include <syslog.h> #include <string.h> +#include <stdlib.h> #include "config.h" @@ -16,160 +17,387 @@ #include <gnutls/crypto.h> #include <gnutls/x509.h> #include <gnutls/dane.h> +#include <gnutls/dtls.h> #elif defined(USE_OPENSSL) #include <openssl/ssl.h> +#include <openssl/x509.h> +#include <openssl/err.h> #endif #include "rexmpp.h" +#include "rexmpp_digest.h" #include "rexmpp_tls.h" +#ifdef ENABLE_CALLS +rexmpp_tls_t *rexmpp_jingle_component_dtls(void *p); +ssize_t +rexmpp_jingle_dtls_push_func (void *p, const void *data, size_t size); +int rexmpp_jingle_dtls_pull_timeout_func (void *p, + unsigned int ms); +#endif + #if defined(USE_OPENSSL) -rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, int ret) { - int err = SSL_get_error(s->tls.openssl_conn, ret); - s->tls.openssl_direction = REXMPP_OPENSSL_NONE; +rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *func, + int ret) +{ + int err = SSL_get_error(tls_ctx->openssl_conn, ret); + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; if (ret == 1) { return REXMPP_TLS_SUCCESS; } else if (err == SSL_ERROR_WANT_READ) { - s->tls.openssl_direction = REXMPP_OPENSSL_READ; + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; return REXMPP_TLS_E_AGAIN; } else if (err == SSL_ERROR_WANT_WRITE) { - s->tls.openssl_direction = REXMPP_OPENSSL_WRITE; + tls_ctx->openssl_direction = REXMPP_OPENSSL_WRITE; return REXMPP_TLS_E_AGAIN; } else { - rexmpp_log(s, LOG_ERR, "OpenSSL error %d", err); + rexmpp_log(s, LOG_ERR, "OpenSSL error %d (ret %d) in %s", + err, ret, func); + ERR_print_errors_fp(stderr); return REXMPP_TLS_E_OTHER; } } #endif -int rexmpp_tls_init (rexmpp_t *s) { +rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls) { + rexmpp_tls_t *tls_ctx = malloc(sizeof(rexmpp_tls_t)); + if (tls_ctx == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to allocate memory for a TLS context"); + return NULL; + } #if defined(USE_GNUTLS) int err; - s->tls.tls_session_data = NULL; - s->tls.tls_session_data_size = 0; + tls_ctx->tls_session_data = NULL; + tls_ctx->tls_session_data_size = 0; - err = gnutls_certificate_allocate_credentials(&(s->tls.gnutls_cred)); + err = gnutls_certificate_allocate_credentials(&(tls_ctx->gnutls_cred)); if (err) { rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", gnutls_strerror(err)); - return 1; + free(tls_ctx); + return NULL; + } + if (! dtls) { + err = gnutls_certificate_set_x509_system_trust(tls_ctx->gnutls_cred); } - err = gnutls_certificate_set_x509_system_trust(s->tls.gnutls_cred); if (err < 0) { rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s", gnutls_strerror(err)); - return 1; + free(tls_ctx); + return NULL; } -#ifdef ENABLE_CALLS - err = gnutls_certificate_allocate_credentials(&(s->jingle.dtls_cred)); - if (err) { - gnutls_certificate_free_credentials(s->tls.gnutls_cred); - rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s", - gnutls_strerror(err)); - return 1; - } -#endif - return 0; + + tls_ctx->dtls_buf_len = 0; #elif defined(USE_OPENSSL) - SSL_library_init(); - SSL_load_error_strings(); - s->tls.openssl_direction = REXMPP_OPENSSL_NONE; - s->tls.openssl_conn = NULL; - s->tls.openssl_ctx = SSL_CTX_new(TLS_client_method()); - if (s->tls.openssl_ctx == NULL) { + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; + tls_ctx->openssl_conn = NULL; + tls_ctx->openssl_ctx = SSL_CTX_new(dtls + ? DTLS_method() + : TLS_method()); + if (tls_ctx->openssl_ctx == NULL) { rexmpp_log(s, LOG_CRIT, "OpenSSL context creation error"); - return 1; + free(tls_ctx); + return NULL; } - SSL_CTX_set_verify(s->tls.openssl_ctx, SSL_VERIFY_PEER, NULL); - if (SSL_CTX_set_default_verify_paths(s->tls.openssl_ctx) == 0) { - rexmpp_log(s, LOG_CRIT, "Failed to set default verify paths for OpenSSL context"); - SSL_CTX_free(s->tls.openssl_ctx); - s->tls.openssl_ctx = NULL; - return 1; + SSL_CTX_set_verify(tls_ctx->openssl_ctx, SSL_VERIFY_PEER, NULL); + if (SSL_CTX_set_default_verify_paths(tls_ctx->openssl_ctx) == 0) { + rexmpp_log(s, LOG_CRIT, + "Failed to set default verify paths for OpenSSL context"); + SSL_CTX_free(tls_ctx->openssl_ctx); + tls_ctx->openssl_ctx = NULL; + free(tls_ctx); + return NULL; } - return 0; #else (void)s; - return 0; + (void)dtls; #endif + return tls_ctx; } +void rexmpp_tls_ctx_free (rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + gnutls_certificate_free_credentials(tls_ctx->gnutls_cred); + if (tls_ctx->tls_session_data != NULL) { + free(tls_ctx->tls_session_data); + tls_ctx->tls_session_data = NULL; + } +#elif defined(USE_OPENSSL) + if (tls_ctx->openssl_ctx != NULL) { + SSL_CTX_free(tls_ctx->openssl_ctx); + } + tls_ctx->openssl_ctx = NULL; +#endif + free(tls_ctx); +} -void rexmpp_tls_cleanup (rexmpp_t *s) { - if (s->tls_state != REXMPP_TLS_INACTIVE && - s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { +int rexmpp_tls_init (rexmpp_t *s) { +#if defined(USE_OPENSSL) + SSL_library_init(); + SSL_load_error_strings(); +#endif + s->tls = rexmpp_tls_ctx_new(s, 0); + return (s->tls == NULL); +} + +void rexmpp_tls_session_free (rexmpp_tls_t *tls_ctx) { #if defined(USE_GNUTLS) - gnutls_deinit(s->tls.gnutls_session); + gnutls_deinit(tls_ctx->gnutls_session); #elif defined(USE_OPENSSL) - if (s->tls.openssl_conn != NULL) { - SSL_free(s->tls.openssl_conn); - s->tls.openssl_conn = NULL; + if (tls_ctx->openssl_conn != NULL) { + SSL_free(tls_ctx->openssl_conn); + tls_ctx->openssl_conn = NULL; + /* bio_conn is freed implicitly by SSL_free. */ + tls_ctx->bio_conn = NULL; + } + if (tls_ctx->bio_io != NULL) { + BIO_free(tls_ctx->bio_io); + tls_ctx->bio_io = NULL; } - s->tls.openssl_direction = REXMPP_OPENSSL_NONE; + tls_ctx->openssl_direction = REXMPP_OPENSSL_NONE; #else - (void)s; + (void)tls_ctx; #endif +} + +void rexmpp_tls_cleanup (rexmpp_t *s) { + if (s->tls_state != REXMPP_TLS_INACTIVE && + s->tls_state != REXMPP_TLS_AWAITING_DIRECT) { + rexmpp_tls_session_free(s->tls); } } void rexmpp_tls_deinit (rexmpp_t *s) { + if (s->tls != NULL) { + rexmpp_tls_ctx_free(s->tls); + s->tls = NULL; + } +} + +#ifdef ENABLE_CALLS #if defined(USE_GNUTLS) - gnutls_certificate_free_credentials(s->tls.gnutls_cred); - if (s->tls.tls_session_data != NULL) { - free(s->tls.tls_session_data); - s->tls.tls_session_data = NULL; +ssize_t +rexmpp_dtls_jingle_pull_func_gnutls (gnutls_transport_ptr_t p, + void *data, + size_t size) +{ + rexmpp_tls_t *tls_ctx = rexmpp_jingle_component_dtls(p); + ssize_t received; + + char *tls_buf = tls_ctx->dtls_buf; + size_t *tls_buf_len = &(tls_ctx->dtls_buf_len); + + rexmpp_tls_err_t ret = REXMPP_TLS_SUCCESS; + if (*tls_buf_len > 0) { + if (size >= *tls_buf_len) { + memcpy(data, tls_buf, *tls_buf_len); + received = *tls_buf_len; + *tls_buf_len = 0; + } else { + if (size > DTLS_SRTP_BUF_SIZE) { + size = DTLS_SRTP_BUF_SIZE; + } + memcpy(data, tls_buf, size); + memmove(tls_buf, tls_buf + size, DTLS_SRTP_BUF_SIZE - size); + received = size; + *tls_buf_len = *tls_buf_len - size; + } + } else { + ret = REXMPP_TLS_E_AGAIN; + } + + if (ret == REXMPP_TLS_SUCCESS) { + return received; + } else if (ret == REXMPP_TLS_E_AGAIN) { + gnutls_transport_set_errno(tls_ctx->gnutls_session, EAGAIN); } + return -1; +} +#endif + +#if defined(USE_OPENSSL) +long rexmpp_dtls_openssl_bio_cb(BIO *b, int oper, const char *argp, + size_t len, int argi, + long argl, int ret, size_t *processed) { + (void)argi; + (void)argl; + (void)processed; + if (oper == BIO_CB_WRITE) { + rexmpp_jingle_dtls_push_func(BIO_get_callback_arg(b), argp, len); + } + return ret; +} +#endif +#endif + +#if defined(USE_OPENSSL) +int rexmpp_openssl_verify_accept_all (int preverify_ok, + X509_STORE_CTX *x509_ctx) +{ + (void)preverify_ok; + (void)x509_ctx; + return 1; +} +#endif + #ifdef ENABLE_CALLS - gnutls_certificate_free_credentials(s->jingle.dtls_cred); +rexmpp_tls_err_t +rexmpp_dtls_connect (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *user_data, + int client) { +#if defined(USE_GNUTLS) + gnutls_session_t *tls_session = &(tls_ctx->gnutls_session); + gnutls_init(tls_session, + (client ? GNUTLS_CLIENT : GNUTLS_SERVER) | + GNUTLS_DATAGRAM | + GNUTLS_NONBLOCK); + if (! client) { + gnutls_certificate_server_set_request(*tls_session, GNUTLS_CERT_REQUIRE); + } + gnutls_set_default_priority(*tls_session); + rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL); + gnutls_credentials_set(*tls_session, GNUTLS_CRD_CERTIFICATE, + tls_ctx->gnutls_cred); + + gnutls_transport_set_ptr(*tls_session, user_data); + gnutls_transport_set_push_function + (*tls_session, rexmpp_jingle_dtls_push_func); + gnutls_transport_set_pull_function + (*tls_session, rexmpp_dtls_jingle_pull_func_gnutls); + gnutls_transport_set_pull_timeout_function + (*tls_session, rexmpp_jingle_dtls_pull_timeout_func); + /* todo: use the profile/crypto-suite from <crypto/> element */ + gnutls_srtp_set_profile(*tls_session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80); + return REXMPP_TLS_SUCCESS; +#elif defined(USE_OPENSSL) + (void)client; + int err; + /* Setup credentials */ + rexmpp_tls_set_x509_key_file(s, tls_ctx, NULL, NULL); + /* Create a connection. */ + tls_ctx->openssl_conn = SSL_new(tls_ctx->openssl_ctx); + SSL_set_verify(tls_ctx->openssl_conn, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + rexmpp_openssl_verify_accept_all); + /* Set a BIO */ + BIO_new_bio_pair(&(tls_ctx->bio_conn), 4096, &(tls_ctx->bio_io), 4096); + BIO_up_ref(tls_ctx->bio_conn); + SSL_set0_rbio(tls_ctx->openssl_conn, tls_ctx->bio_conn); + SSL_set0_wbio(tls_ctx->openssl_conn, tls_ctx->bio_conn); + /* Set a callback to track writes */ + BIO_set_callback_ex(tls_ctx->bio_conn, rexmpp_dtls_openssl_bio_cb); + BIO_set_callback_arg(tls_ctx->bio_conn, user_data); + BIO_set_ssl(tls_ctx->bio_conn, tls_ctx->openssl_conn, BIO_NOCLOSE); + /* Enable SRTP (TODO: support different profiles) */ + err = SSL_set_tlsext_use_srtp(tls_ctx->openssl_conn, + "SRTP_AES128_CM_SHA1_80"); + if (err) { + rexmpp_log(s, LOG_ERR, "Failed to setup SRTP for the DTLS connection"); + return REXMPP_TLS_E_OTHER; + } + if (client) { + err = SSL_connect(tls_ctx->openssl_conn); + } else { + err = SSL_accept(tls_ctx->openssl_conn); + } + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_dtls_connect", err); +#else + (void)s; + (void)tls_ctx; + (void)user_data; + (void)client; + return REXMPP_TLS_E_OTHER; #endif +} + +void rexmpp_dtls_feed(rexmpp_t *s, rexmpp_tls_t *tls_ctx, uint8_t *buf, size_t len) { +#if defined(USE_GNUTLS) + if (tls_ctx->dtls_buf_len + len < DTLS_SRTP_BUF_SIZE) { + memcpy(tls_ctx->dtls_buf + tls_ctx->dtls_buf_len, buf, len); + tls_ctx->dtls_buf_len += len; + } else { + rexmpp_log(s, LOG_WARNING, "Dropping a DTLS packet"); + } #elif defined(USE_OPENSSL) - if (s->tls.openssl_ctx != NULL) { - SSL_CTX_free(s->tls.openssl_ctx); + (void)s; + BIO_write(tls_ctx->bio_io, buf, len); +#else + (void)s; + (void)tls_ctx; + (void)buf; + (void)len; +#endif +} +#endif + +rexmpp_tls_err_t rexmpp_tls_handshake (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { +#if defined(USE_GNUTLS) + int ret = gnutls_handshake(tls_ctx->gnutls_session); + if (ret == 0) { + return REXMPP_TLS_SUCCESS; + } else if (ret == GNUTLS_E_AGAIN) { + return REXMPP_TLS_E_AGAIN; + } else { + rexmpp_log(s, LOG_ERR, "Error during a TLS handshake: %s", + gnutls_strerror(ret)); + return REXMPP_TLS_E_OTHER; } - s->tls.openssl_ctx = NULL; +#elif defined(USE_OPENSSL) + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_handshake", + SSL_do_handshake(tls_ctx->openssl_conn)); #else (void)s; + (void)tls_ctx; + return REXMPP_TLS_E_OTHER; #endif } rexmpp_tls_err_t rexmpp_tls_connect (rexmpp_t *s) { + if (s->x509_key_file != NULL && s->x509_cert_file != NULL) { + rexmpp_tls_set_x509_key_file(s, s->tls, NULL, NULL); + } + #if defined(USE_GNUTLS) if (s->tls_state != REXMPP_TLS_HANDSHAKE) { - gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")}; + gnutls_datum_t xmpp_client_protocol = + {(unsigned char*)"xmpp-client", strlen("xmpp-client")}; rexmpp_log(s, LOG_DEBUG, "starting TLS"); - gnutls_init(&s->tls.gnutls_session, GNUTLS_CLIENT); - gnutls_session_set_ptr(s->tls.gnutls_session, s); - gnutls_alpn_set_protocols(s->tls.gnutls_session, &xmpp_client_protocol, 1, 0); - gnutls_server_name_set(s->tls.gnutls_session, GNUTLS_NAME_DNS, + gnutls_init(&s->tls->gnutls_session, GNUTLS_CLIENT); + gnutls_session_set_ptr(s->tls->gnutls_session, s); + gnutls_alpn_set_protocols(s->tls->gnutls_session, &xmpp_client_protocol, 1, 0); + gnutls_server_name_set(s->tls->gnutls_session, GNUTLS_NAME_DNS, s->initial_jid.domain, strlen(s->initial_jid.domain)); - gnutls_set_default_priority(s->tls.gnutls_session); - gnutls_credentials_set(s->tls.gnutls_session, GNUTLS_CRD_CERTIFICATE, - s->tls.gnutls_cred); - gnutls_transport_set_int(s->tls.gnutls_session, s->server_socket); - gnutls_handshake_set_timeout(s->tls.gnutls_session, + gnutls_set_default_priority(s->tls->gnutls_session); + gnutls_credentials_set(s->tls->gnutls_session, GNUTLS_CRD_CERTIFICATE, + s->tls->gnutls_cred); + gnutls_transport_set_int(s->tls->gnutls_session, s->server_socket); + gnutls_handshake_set_timeout(s->tls->gnutls_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); - if (s->tls.tls_session_data != NULL) { - int ret = gnutls_session_set_data(s->tls.gnutls_session, - s->tls.tls_session_data, - s->tls.tls_session_data_size); + if (s->tls->tls_session_data != NULL) { + int ret = gnutls_session_set_data(s->tls->gnutls_session, + s->tls->tls_session_data, + s->tls->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.tls_session_data); - s->tls.tls_session_data = NULL; - s->tls.tls_session_data_size = 0; + free(s->tls->tls_session_data); + s->tls->tls_session_data = NULL; + s->tls->tls_session_data_size = 0; } } } - int ret = gnutls_handshake(s->tls.gnutls_session); + int ret = gnutls_handshake(s->tls->gnutls_session); if (ret == GNUTLS_E_AGAIN) { rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete"); return REXMPP_TLS_E_AGAIN; } else if (ret == 0) { - int status; + unsigned int status; int srv_is_secure = 0; if (s->stream_state == REXMPP_STREAM_NONE && @@ -190,7 +418,7 @@ rexmpp_tls_connect (rexmpp_t *s) { service/source host (<https://tools.ietf.org/html/rfc7712#section-5.1>, <https://tools.ietf.org/html/rfc7673#section-6>). */ - ret = dane_verify_session_crt(NULL, s->tls.gnutls_session, s->server_host, + ret = dane_verify_session_crt(NULL, s->tls->gnutls_session, s->server_host, "tcp", s->server_port, 0, 0, &status); if (ret) { rexmpp_log(s, LOG_WARNING, "DANE verification error: %s", @@ -212,7 +440,7 @@ rexmpp_tls_connect (rexmpp_t *s) { } } - ret = gnutls_certificate_verify_peers3(s->tls.gnutls_session, + ret = gnutls_certificate_verify_peers3(s->tls->gnutls_session, s->initial_jid.domain, &status); if (ret || status) { @@ -224,23 +452,23 @@ rexmpp_tls_connect (rexmpp_t *s) { } else { rexmpp_log(s, LOG_ERR, "Untrusted certificate"); } - gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR); + gnutls_bye(s->tls->gnutls_session, GNUTLS_SHUT_RDWR); return REXMPP_TLS_E_OTHER; } - if (gnutls_session_is_resumed(s->tls.gnutls_session)) { + if (gnutls_session_is_resumed(s->tls->gnutls_session)) { rexmpp_log(s, LOG_INFO, "TLS session is resumed"); } else { - if (s->tls.tls_session_data != NULL) { + if (s->tls->tls_session_data != NULL) { rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed"); - free(s->tls.tls_session_data); - s->tls.tls_session_data = NULL; + free(s->tls->tls_session_data); + s->tls->tls_session_data = NULL; } - gnutls_session_get_data(s->tls.gnutls_session, NULL, - &s->tls.tls_session_data_size); - s->tls.tls_session_data = malloc(s->tls.tls_session_data_size); - ret = gnutls_session_get_data(s->tls.gnutls_session, s->tls.tls_session_data, - &s->tls.tls_session_data_size); + gnutls_session_get_data(s->tls->gnutls_session, NULL, + &s->tls->tls_session_data_size); + s->tls->tls_session_data = malloc(s->tls->tls_session_data_size); + ret = gnutls_session_get_data(s->tls->gnutls_session, s->tls->tls_session_data, + &s->tls->tls_session_data_size); if (ret != GNUTLS_E_SUCCESS) { rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s", gnutls_strerror(ret)); @@ -256,26 +484,27 @@ rexmpp_tls_connect (rexmpp_t *s) { } #elif defined(USE_OPENSSL) if (s->tls_state != REXMPP_TLS_HANDSHAKE) { - s->tls.openssl_conn = SSL_new(s->tls.openssl_ctx); - if (s->tls.openssl_conn == NULL) { + s->tls->openssl_conn = SSL_new(s->tls->openssl_ctx); + if (s->tls->openssl_conn == NULL) { rexmpp_log(s, LOG_ERR, "Failed to create an OpenSSL connection object"); return REXMPP_TLS_E_OTHER; } - if (SSL_set_fd(s->tls.openssl_conn, s->server_socket) == 0) { + if (SSL_set_fd(s->tls->openssl_conn, s->server_socket) == 0) { rexmpp_log(s, LOG_ERR, "Failed to set a file descriptor for OpenSSL connection"); return REXMPP_TLS_E_OTHER; } - if (SSL_set1_host(s->tls.openssl_conn, s->initial_jid.domain) == 0) { + if (SSL_set1_host(s->tls->openssl_conn, s->initial_jid.domain) == 0) { rexmpp_log(s, LOG_ERR, "Failed to set a hostname for OpenSSL connection"); return REXMPP_TLS_E_OTHER; } /* For SNI */ - if (SSL_set_tlsext_host_name(s->tls.openssl_conn, s->initial_jid.domain) == 0) { + if (SSL_set_tlsext_host_name(s->tls->openssl_conn, s->initial_jid.domain) == 0) { rexmpp_log(s, LOG_ERR, "Failed to set a tlsext hostname for OpenSSL connection"); return REXMPP_TLS_E_OTHER; } } - return rexmpp_process_openssl_ret(s, SSL_connect(s->tls.openssl_conn)); + return rexmpp_process_openssl_ret(s, s->tls, "rexmpp_tls_connect", + SSL_connect(s->tls->openssl_conn)); #else rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; @@ -283,36 +512,86 @@ rexmpp_tls_connect (rexmpp_t *s) { } rexmpp_tls_err_t -rexmpp_tls_disconnect (rexmpp_t *s) { +rexmpp_tls_disconnect (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { #if defined(USE_GNUTLS) - int ret = gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR); + int ret = gnutls_bye(tls_ctx->gnutls_session, GNUTLS_SHUT_RDWR); if (ret == GNUTLS_E_SUCCESS) { return REXMPP_TLS_SUCCESS; + } else if (ret == GNUTLS_E_AGAIN) { + return REXMPP_TLS_E_AGAIN; } else { rexmpp_log(s, LOG_WARNING, "Failed to close TLS connection: %s", gnutls_strerror(ret)); return REXMPP_TLS_E_OTHER; } #elif defined(USE_OPENSSL) - int ret = SSL_shutdown(s->tls.openssl_conn); + int ret = SSL_shutdown(tls_ctx->openssl_conn); if (ret == 0) { - s->tls.openssl_direction = REXMPP_OPENSSL_READ; + tls_ctx->openssl_direction = REXMPP_OPENSSL_READ; return REXMPP_TLS_E_AGAIN; } else { - return rexmpp_process_openssl_ret(s, ret); + return rexmpp_process_openssl_ret(s, tls_ctx, + "rexmpp_tls_disconnect", ret); } #else + (void)tls_ctx; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; #endif } -rexmpp_tls_err_t -rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) +#ifdef ENABLE_CALLS +int +rexmpp_tls_srtp_get_keys (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + size_t key_len, + size_t salt_len, + unsigned char *key_mat) { #if defined(USE_GNUTLS) + int key_mat_size; + key_mat_size = + gnutls_srtp_get_keys(tls_ctx->gnutls_session, + key_mat, (key_len + salt_len) * 2, + NULL, NULL, NULL, NULL); + if (key_mat_size == GNUTLS_E_SHORT_MEMORY_BUFFER || + key_mat_size < 0) { + rexmpp_log(s, LOG_ERR, + "Failed to retrieve DTLS key material for SRTP: %s", + gnutls_strerror(key_mat_size)); + } + return 0; +#elif defined(USE_OPENSSL) + /* https://www.rfc-editor.org/rfc/rfc5764.html */ + const char *extractor = "EXTRACTOR-dtls_srtp"; + int err = SSL_export_keying_material(tls_ctx->openssl_conn, + key_mat, 2 * (key_len + salt_len), + extractor, strlen(extractor), + NULL, 0, 0); + return rexmpp_process_openssl_ret(s, tls_ctx, + "rexmpp_tls_srtp_get_keys", err); +#else + (void)s; + (void)tls_ctx; + (void)key_len; + (void)salt_len; + (void)key_mat; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return -1; +#endif +} +#endif + +rexmpp_tls_err_t +rexmpp_tls_send (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *written) +{ *written = -1; - ssize_t ret = gnutls_record_send(s->tls.gnutls_session, +#if defined(USE_GNUTLS) + ssize_t ret = gnutls_record_send(tls_ctx->gnutls_session, data, data_size); if (ret >= 0) { @@ -325,27 +604,32 @@ rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written) return REXMPP_TLS_E_OTHER; } #elif defined(USE_OPENSSL) - *written = -1; - int ret = SSL_write_ex(s->tls.openssl_conn, data, data_size, written); + int ret = SSL_write_ex(tls_ctx->openssl_conn, data, data_size, + (size_t*)written); if (ret > 0) { return REXMPP_TLS_SUCCESS; } else { - return rexmpp_process_openssl_ret(s, ret); + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_send", ret); } #else (void)data; (void)data_size; - (void)written; + (void)tls_ctx; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; #endif } rexmpp_tls_err_t -rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) { +rexmpp_tls_recv (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *received) +{ #if defined(USE_GNUTLS) *received = -1; - ssize_t ret = gnutls_record_recv(s->tls.gnutls_session, data, data_size); + ssize_t ret = gnutls_record_recv(tls_ctx->gnutls_session, data, data_size); if (ret >= 0) { *received = ret; return REXMPP_TLS_SUCCESS; @@ -357,35 +641,49 @@ rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) { } #elif defined(USE_OPENSSL) *received = -1; - int ret = SSL_read_ex(s->tls.openssl_conn, data, data_size, received); + int ret = SSL_read_ex(tls_ctx->openssl_conn, data, data_size, + (size_t*)received); if (ret > 0) { return REXMPP_TLS_SUCCESS; } else { - return rexmpp_process_openssl_ret(s, ret); + return rexmpp_process_openssl_ret(s, tls_ctx, "rexmpp_tls_recv", ret); } #else (void)data; (void)data_size; (void)received; + (void)tls_ctx; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; #endif } +#ifdef ENABLE_CALLS +unsigned int rexmpp_dtls_timeout (rexmpp_t *s, rexmpp_tls_t *tls_ctx) { + (void)s; +#if defined(USE_GNUTLS) + return gnutls_dtls_get_timeout(tls_ctx->gnutls_session); +#else + (void)tls_ctx; + return -1; +#endif +} +#endif + int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { #if defined(USE_GNUTLS) - if (gnutls_record_get_direction(s->tls.gnutls_session) == 0) { + if (gnutls_record_get_direction(s->tls->gnutls_session) == 0) { FD_SET(s->server_socket, read_fds); } else { FD_SET(s->server_socket, write_fds); } return s->server_socket + 1; #elif defined(USE_OPENSSL) - if (s->tls.openssl_direction == REXMPP_OPENSSL_READ) { + if (s->tls->openssl_direction == REXMPP_OPENSSL_READ) { FD_SET(s->server_socket, read_fds); return s->server_socket + 1; } - if (s->tls.openssl_direction == REXMPP_OPENSSL_WRITE) { + if (s->tls->openssl_direction == REXMPP_OPENSSL_WRITE) { FD_SET(s->server_socket, write_fds); return s->server_socket + 1; } @@ -400,20 +698,25 @@ int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { rexmpp_tls_err_t rexmpp_tls_set_x509_key_file (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, const char *cert_file, const char *key_file) { + if (cert_file == NULL) { + cert_file = s->x509_cert_file; + } + if (key_file == NULL) { + key_file = s->x509_key_file; + } + if (cert_file == NULL || key_file == NULL) { + rexmpp_log(s, LOG_ERR, "No certificate or key file defined"); + return REXMPP_TLS_E_OTHER; + } #if defined(USE_GNUTLS) - int ret = gnutls_certificate_set_x509_key_file(s->tls.gnutls_cred, + int ret = gnutls_certificate_set_x509_key_file(tls_ctx->gnutls_cred, cert_file, key_file, - GNUTLS_X509_FMT_PEM); -#ifdef ENABLE_CALLS - gnutls_certificate_set_x509_key_file(s->jingle.dtls_cred, - cert_file, - key_file, - GNUTLS_X509_FMT_PEM); -#endif + GNUTLS_X509_FMT_DER); if (ret == 0) { return REXMPP_TLS_SUCCESS; } else { @@ -422,15 +725,15 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, return REXMPP_TLS_E_OTHER; } #elif defined(USE_OPENSSL) - if (SSL_CTX_use_certificate_file(s->tls.openssl_ctx, + if (SSL_CTX_use_certificate_file(tls_ctx->openssl_ctx, cert_file, - SSL_FILETYPE_PEM) != 1) { + SSL_FILETYPE_ASN1) != 1) { rexmpp_log(s, LOG_ERR, "Failed to set a certificate file"); return REXMPP_TLS_E_OTHER; } - if (SSL_CTX_use_PrivateKey_file(s->tls.openssl_ctx, + if (SSL_CTX_use_PrivateKey_file(tls_ctx->openssl_ctx, key_file, - SSL_FILETYPE_PEM) != 1) { + SSL_FILETYPE_ASN1) != 1) { rexmpp_log(s, LOG_ERR, "Failed to set a key file"); return REXMPP_TLS_E_OTHER; } @@ -438,6 +741,7 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, #else (void)cert_file; (void)key_file; + (void)tls_ctx; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; #endif @@ -445,22 +749,248 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, rexmpp_tls_err_t rexmpp_tls_set_x509_trust_file (rexmpp_t *s, - const char *cert_file) + rexmpp_tls_t *tls_ctx, + const char *trust_file) { + if (trust_file == NULL) { + trust_file = s->x509_trust_file; + } + if (trust_file == NULL) { + rexmpp_log(s, LOG_ERR, "No trust file is defined"); + return REXMPP_TLS_E_OTHER; + } #if defined(USE_GNUTLS) - gnutls_certificate_set_x509_trust_file(s->tls.gnutls_cred, - cert_file, - GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_x509_trust_file(tls_ctx->gnutls_cred, + trust_file, + GNUTLS_X509_FMT_DER); return REXMPP_TLS_SUCCESS; #elif defined(USE_OPENSSL) - if (SSL_CTX_load_verify_locations(s->tls.openssl_ctx, cert_file, NULL) != 1) { + if (SSL_CTX_load_verify_locations(tls_ctx->openssl_ctx, trust_file, NULL) != 1) { rexmpp_log(s, LOG_ERR, "Failed to set a trusted certificate file"); return REXMPP_TLS_E_OTHER; } return REXMPP_TLS_SUCCESS; #else - (void)cert_file; + (void)trust_file; + (void)tls_ctx; rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); return REXMPP_TLS_E_OTHER; #endif } + + +int rexmpp_tls_peer_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + unsigned int cert_list_size = 0; + const gnutls_datum_t *cert_list; + cert_list = + gnutls_certificate_get_peers(tls_ctx->gnutls_session, &cert_list_size); + if (cert_list_size != 1) { + rexmpp_log(s, LOG_ERR, + "Unexpected peer certificate list size: %d", + cert_list_size); + return -1; + } + return rexmpp_x509_raw_cert_fp(s, algo_str, cert_list, + raw_fp, fp_str, fp_size); +#elif defined(USE_OPENSSL) + if (strcmp(algo_str, "sha-256") != 0) { + rexmpp_log(s, LOG_ERR, + "Unsupported hash function algorithm: %s", algo_str); + return -1; + } + X509 *peer_cert = SSL_get0_peer_certificate(tls_ctx->openssl_conn); + if (peer_cert == NULL) { + rexmpp_log(s, LOG_ERR, "No peer certificate found"); + return -1; + } + unsigned int len; + X509_digest(peer_cert, EVP_sha256(), (unsigned char*)raw_fp, &len); + *fp_size = len; + size_t i; + for (i = 0; i < *fp_size; i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[*fp_size * 3 - 1] = 0; + return 0; +#else + (void)tls_ctx; + (void)algo_str; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support"); + return -1; +#endif +} + +/* TODO: handle different algorithms, and maybe apply this to + arbitrary files. */ +int rexmpp_tls_my_fp (rexmpp_t *s, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ + rexmpp_digest_t digest_ctx; + if (rexmpp_digest_init(&digest_ctx, REXMPP_DIGEST_SHA256)) { + rexmpp_log(s, LOG_ERR, "Failed to initialize a digest object"); + return -1; + } + + if (s->x509_cert_file == NULL) { + rexmpp_log(s, LOG_WARNING, "No X.509 certificate file defined"); + return -1; + } + FILE *fh = fopen(s->x509_cert_file, "r"); + if (fh == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to open the X.509 certificate file"); + return -1; + } + unsigned char *buf[4096]; + size_t len = fread(buf, 1, 4096, fh); + while (len > 0) { + rexmpp_digest_update(&digest_ctx, buf, len); + len = fread(buf, 1, 4096, fh); + } + fclose(fh); + + *fp_size = rexmpp_digest_len(REXMPP_DIGEST_SHA256); + rexmpp_digest_finish(&digest_ctx, raw_fp, *fp_size); + + size_t i; + for (i = 0; i < (*fp_size); i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[(*fp_size) * 3 - 1] = 0; + return 0; +} + +int rexmpp_tls_session_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + gnutls_x509_crt_t *cert_list; + unsigned int cert_list_size = 0; + int err = + gnutls_certificate_get_x509_crt(tls_ctx->gnutls_cred, + 0, &cert_list, &cert_list_size); + if (err) { + rexmpp_log(s, LOG_ERR, + "Failed to read own certificate list: %s", + gnutls_strerror(err)); + return -1; + } + + err = rexmpp_x509_cert_fp(s, algo_str, cert_list[0], raw_fp, fp_str, fp_size); + + size_t i; + for (i = 0; i < cert_list_size; i++) { + gnutls_x509_crt_deinit(cert_list[i]); + } + gnutls_free(cert_list); + return err; +#else + (void)s; + (void)tls_ctx; + (void)algo_str; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} + +int rexmpp_x509_cert_fp (rexmpp_t *s, + const char *algo_str, + void *cert, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + gnutls_datum_t raw_cert; + int err = gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &raw_cert); + if (err != GNUTLS_E_SUCCESS) { + rexmpp_log(s, LOG_ERR, "Failed to export a certificate: %s", + gnutls_strerror(err)); + return err; + } + err = rexmpp_x509_raw_cert_fp(s, algo_str, &raw_cert, raw_fp, fp_str, fp_size); + gnutls_free(raw_cert.data); + return err; +#else + (void)s; + (void)algo_str; + (void)cert; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} + +int rexmpp_x509_raw_cert_fp (rexmpp_t *s, + const char *algo_str, + const void *raw_cert, + char *raw_fp, + char *fp_str, + size_t *fp_size) +{ +#if defined(USE_GNUTLS) + const gnutls_datum_t *cert = (const gnutls_datum_t*)raw_cert; + gnutls_digest_algorithm_t algo = GNUTLS_DIG_UNKNOWN; + /* gnutls_digest_get_id uses different names, so + checking manually here. These are SDP options, + <https://datatracker.ietf.org/doc/html/rfc4572#page-8>. */ + if (strcmp(algo_str, "sha-1") == 0) { + algo = GNUTLS_DIG_SHA1; + } else if (strcmp(algo_str, "sha-224") == 0) { + algo = GNUTLS_DIG_SHA224; + } else if (strcmp(algo_str, "sha-256") == 0) { + algo = GNUTLS_DIG_SHA256; + } else if (strcmp(algo_str, "sha-384") == 0) { + algo = GNUTLS_DIG_SHA384; + } else if (strcmp(algo_str, "sha-512") == 0) { + algo = GNUTLS_DIG_SHA512; + } else if (strcmp(algo_str, "md5") == 0) { + algo = GNUTLS_DIG_MD5; + } + if (algo == GNUTLS_DIG_UNKNOWN) { + rexmpp_log(s, LOG_ERR, "Unknown hash algorithm: %s", algo_str); + return -1; + } + + int err = gnutls_fingerprint(algo, cert, raw_fp, fp_size); + if (err != GNUTLS_E_SUCCESS) { + rexmpp_log(s, LOG_ERR, "Failed to calculate a fingerprint: %s", + gnutls_strerror(err)); + return -1; + } + if (fp_str != NULL) { + size_t i; + for (i = 0; i < (*fp_size); i++) { + snprintf(fp_str + i * 3, 4, "%02X:", raw_fp[i] & 0xFF); + } + fp_str[(*fp_size) * 3 - 1] = 0; + } + return 0; +#else + (void)s; + (void)algo_str; + (void)raw_cert; + (void)raw_fp; + (void)fp_str; + (void)fp_size; + return -1; +#endif +} diff --git a/src/rexmpp_tls.h b/src/rexmpp_tls.h index 24ba042..ff4bca6 100644 --- a/src/rexmpp_tls.h +++ b/src/rexmpp_tls.h @@ -20,6 +20,8 @@ to write logs and read other values (including server socket). #include "rexmpp.h" #include "config.h" +#define DTLS_SRTP_BUF_SIZE 0x4000 + typedef struct rexmpp_tls rexmpp_tls_t; /** @@ -43,6 +45,8 @@ struct rexmpp_tls { size_t tls_session_data_size; gnutls_session_t gnutls_session; gnutls_certificate_credentials_t gnutls_cred; + char dtls_buf[DTLS_SRTP_BUF_SIZE]; + size_t dtls_buf_len; }; #elif defined(USE_OPENSSL) #include <openssl/ssl.h> @@ -54,6 +58,8 @@ enum rexmpp_openssl_direction { struct rexmpp_tls { SSL_CTX *openssl_ctx; SSL *openssl_conn; + BIO *bio_conn; + BIO *bio_io; enum rexmpp_openssl_direction openssl_direction; }; #else @@ -66,26 +72,93 @@ int rexmpp_tls_init(rexmpp_t *s); void rexmpp_tls_cleanup(rexmpp_t *s); void rexmpp_tls_deinit(rexmpp_t *s); -rexmpp_tls_err_t rexmpp_tls_connect(rexmpp_t *s); -rexmpp_tls_err_t rexmpp_tls_disconnect(rexmpp_t *s); +rexmpp_tls_t *rexmpp_tls_ctx_new (rexmpp_t *s, int dtls); +void rexmpp_tls_ctx_free (rexmpp_tls_t *tls_ctx); + +void rexmpp_tls_session_free (rexmpp_tls_t *tls_ctx); -rexmpp_tls_err_t rexmpp_tls_send(rexmpp_t *s, void *data, size_t data_size, ssize_t *written); -rexmpp_tls_err_t rexmpp_tls_recv(rexmpp_t *s, void *data, size_t data_size, ssize_t *received); +rexmpp_tls_err_t rexmpp_tls_connect (rexmpp_t *s); +rexmpp_tls_err_t rexmpp_tls_handshake (rexmpp_t *s, rexmpp_tls_t *tls_ctx); +rexmpp_tls_err_t rexmpp_tls_disconnect (rexmpp_t *s, rexmpp_tls_t *tls_ctx); +#ifdef ENABLE_CALLS +rexmpp_tls_err_t +rexmpp_dtls_connect (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *user_data, + int client); +void rexmpp_dtls_feed(rexmpp_t *s, rexmpp_tls_t *tls_ctx, uint8_t *buf, size_t len); + +int +rexmpp_tls_srtp_get_keys (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + size_t key_len, + size_t salt_len, + unsigned char *key_mat); +#endif +rexmpp_tls_err_t +rexmpp_tls_send (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *written); +rexmpp_tls_err_t +rexmpp_tls_recv (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + void *data, + size_t data_size, + ssize_t *received); +#ifdef ENABLE_CALLS +unsigned int rexmpp_dtls_timeout (rexmpp_t *s, rexmpp_tls_t *tls_ctx); +#endif int rexmpp_tls_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds); /** - @brief Sets credentials for both client authentication to the - server (SASL EXTERNAL) and DTLS connections in Jingle sessions. + @brief Sets credentials for a given TLS context: either provided + ones or defined for the whole ::rexmpp structure. */ rexmpp_tls_err_t rexmpp_tls_set_x509_key_file (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, const char *cert_file, const char *key_file); rexmpp_tls_err_t rexmpp_tls_set_x509_trust_file (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, const char *cert_file); +int rexmpp_tls_peer_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size); + +int rexmpp_tls_my_fp (rexmpp_t *s, + char *raw_fp, + char *fp_str, + size_t *fp_size); + +int rexmpp_tls_session_fp (rexmpp_t *s, + rexmpp_tls_t *tls_ctx, + const char *algo_str, + char *raw_fp, + char *fp_str, + size_t *fp_size); + +int rexmpp_x509_cert_fp (rexmpp_t *s, + const char *algo_str, + void *cert, + char *raw_fp, + char *fp_str, + size_t *fp_size); + +int rexmpp_x509_raw_cert_fp (rexmpp_t *s, + const char *algo_str, + const void *raw_cert, + char *raw_fp, + char *fp_str, + size_t *fp_size); #endif diff --git a/src/rexmpp_utf8.h b/src/rexmpp_utf8.h new file mode 100644 index 0000000..4096aac --- /dev/null +++ b/src/rexmpp_utf8.h @@ -0,0 +1,93 @@ +/** + @file rexmpp_utf8.h + @brief UTF-8 helper functions + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_UTF8_H +#define REXMPP_UTF8_H + +#include <stddef.h> +#include <stdint.h> + +#ifdef HAVE_ICU + +#include <unicode/utf8.h> +#define REXMPP_U8_NEXT U8_NEXT + +#else + +#define REXMPP_U8_NEXT(str, pos, len, out) \ + rexmpp_utf8_next(str, &pos, len, &out); + +/** + @brief Similar to libicu's U8_NEXT macros: reads a single UTF-8 + code point, advances the position. + @param[in] str A string to read. + @param[in,out] pos Byte position within the string. Advanced by the + number of bytes read to produce a code point, not advanced on + failure. + @param[in] len String length. + @param[in,out] out A pointer to the location for writing the code + point. + @returns 0 on failure, 1 on success. +*/ +inline static +void rexmpp_utf8_next (const uint8_t *str, + size_t *pos, + size_t len, + int32_t *out) +{ + if (*pos >= len) { + *out = -1; + return; + } + + if ((str[*pos] & 0x80) == 0 + && *pos + 1 <= len) + /* U+0000 to U+007F: 0xxxxxxx */ + { + *out = str[*pos]; + *pos = *pos + 1; + } else if ((str[*pos] & 0xe0) == 0xc0 + && *pos + 2 <= len + && (str[*pos + 1] & 0xc0) == 0x80) + /* U+0080 to U+07FF: 110xxxxx 10xxxxxx */ + { + *out = (((int32_t)(str[*pos] & 0x1f) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)); + *pos = *pos + 2; + } else if ((str[*pos] & 0xf0) == 0xe0 + && *pos + 3 <= len + && (str[*pos + 1] & 0xc0) == 0x80 + && (str[*pos + 2] & 0xc0) == 0x80) + /* U+0800 to U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx */ + { + *out = (((((int32_t)(str[*pos] & 0xf) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)) << 6) + | ((int32_t)str[*pos + 2] & 0x3f)); + *pos = *pos + 3; + } else if ((str[*pos] & 0xf8) == 0xf0 + && *pos + 4 <= len + && (str[*pos + 1] & 0xc0) == 0x80 + && (str[*pos + 2] & 0xc0) == 0x80 + && (str[*pos + 3] & 0xc0) == 0x80) + /* U+10000 to U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + { + *out = (((((((int32_t)(str[*pos] & 7) << 6) + | ((int32_t)str[*pos + 1] & 0x3f)) << 6) + | (((int32_t)str[*pos + 2] & 0x3f))) << 6) + | ((int32_t)str[*pos + 3] & 0x3f)); + *pos = *pos + 4; + } else + /* Invalid UTF-8 */ + { + *out = -1; + } +} + +#endif /* HAVE_ICU */ + +#endif /* REXMPP_UTF8_H */ diff --git a/src/rexmpp_xml.c b/src/rexmpp_xml.c new file mode 100644 index 0000000..8b106ab --- /dev/null +++ b/src/rexmpp_xml.c @@ -0,0 +1,805 @@ +/** + @file rexmpp_xml.c + @brief XML structures and functions for rexmpp + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include "rexmpp.h" +#include "rexmpp_utf8.h" +#include "rexmpp_xml.h" +#include "rexmpp_random.h" + +#ifndef USE_RUST +void rexmpp_xml_qname_free (rexmpp_xml_qname_t *qname) { + if (qname->name != NULL) { + free(qname->name); + qname->name = NULL; + } + if (qname->namespace != NULL) { + free(qname->namespace); + qname->namespace = NULL; + } +} + +void rexmpp_xml_attribute_free (rexmpp_xml_attr_t *attr) { + if (attr == NULL) { + return; + } + rexmpp_xml_qname_free(&(attr->qname)); + if (attr->value != NULL) { + free(attr->value); + attr->value = NULL; + } + free(attr); +} + +void rexmpp_xml_attribute_free_list (rexmpp_xml_attr_t *attr) { + rexmpp_xml_attr_t *next = attr; + while (attr != NULL) { + next = attr->next; + rexmpp_xml_attribute_free(attr); + attr = next; + } +} + +void rexmpp_xml_free (rexmpp_xml_t *node) { + if (node == NULL) { + return; + } + if (node->type == REXMPP_XML_TEXT) { + if (node->alt.text != NULL) { + free(node->alt.text); + node->alt.text = NULL; + } + } if (node->type == REXMPP_XML_ELEMENT) { + rexmpp_xml_qname_free(&(node->alt.elem.qname)); + rexmpp_xml_attribute_free_list(node->alt.elem.attributes); + rexmpp_xml_free_list(node->alt.elem.children); + } + free(node); +} + +void rexmpp_xml_free_list (rexmpp_xml_t *node) { + rexmpp_xml_t *next = node; + while (node != NULL) { + next = node->next; + rexmpp_xml_free(node); + node = next; + } +} + +rexmpp_xml_t *rexmpp_xml_clone (rexmpp_xml_t *node) { + if (node == NULL) { + return NULL; + } + + if (node->type == REXMPP_XML_TEXT) { + return rexmpp_xml_new_text(node->alt.text); + } else if (node->type == REXMPP_XML_ELEMENT) { + rexmpp_xml_t *ret = + rexmpp_xml_new_elem(node->alt.elem.qname.name, + node->alt.elem.qname.namespace); + rexmpp_xml_attr_t **next_attr = &(ret->alt.elem.attributes); + rexmpp_xml_attr_t *old_attr; + for (old_attr = node->alt.elem.attributes; + old_attr != NULL; + old_attr = old_attr->next) + { + rexmpp_xml_attr_t *new_attr = + rexmpp_xml_attr_new(old_attr->qname.name, + old_attr->qname.namespace, + old_attr->value); + *next_attr = new_attr; + next_attr = &(new_attr->next); + } + + ret->alt.elem.children = + rexmpp_xml_clone_list(node->alt.elem.children); + return ret; + } + return NULL; +} + +rexmpp_xml_t *rexmpp_xml_clone_list (rexmpp_xml_t *node) { + rexmpp_xml_t *first, *last; + if (node == NULL) { + return NULL; + } + first = rexmpp_xml_clone(node); + for (last = first, node = node->next; + node != NULL; + last = last->next, node = node->next) + { + last->next = rexmpp_xml_clone(node); + } + return first; +} + +rexmpp_xml_t *rexmpp_xml_new_text (const char *str) { + rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t)); + node->type = REXMPP_XML_TEXT; + node->alt.text = strdup(str); + node->next = NULL; + return node; +} + +rexmpp_xml_t *rexmpp_xml_new_text_len (const char *str, size_t len) { + rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t)); + node->type = REXMPP_XML_TEXT; + node->alt.text = strndup(str, len); + node->next = NULL; + return node; +} + +void rexmpp_xml_add_child (rexmpp_xml_t *node, + rexmpp_xml_t *child) +{ + rexmpp_xml_t **last_ptr = &(node->alt.elem.children); + while (*last_ptr != NULL) { + last_ptr = &((*last_ptr)->next); + } + *last_ptr = child; +} + +int rexmpp_xml_add_text (rexmpp_xml_t *node, + const char *str) +{ + rexmpp_xml_t *text_node = rexmpp_xml_new_text(str); + if (text_node != NULL) { + rexmpp_xml_add_child(node, text_node); + return 0; + } + return -1; +} + +int rexmpp_xml_add_text_len (rexmpp_xml_t *node, + const char *str, + size_t len) +{ + rexmpp_xml_t *text_node = rexmpp_xml_new_text_len(str, len); + if (text_node != NULL) { + rexmpp_xml_add_child(node, text_node); + return 0; + } + return -1; +} + +rexmpp_xml_t *rexmpp_xml_new_elem (const char *name, + const char *namespace) +{ + rexmpp_xml_t *node = malloc(sizeof(rexmpp_xml_t)); + node->type = REXMPP_XML_ELEMENT; + node->alt.elem.qname.name = strdup(name); + if (namespace != NULL) { + node->alt.elem.qname.namespace = strdup(namespace); + } else { + node->alt.elem.qname.namespace = NULL; + } + node->alt.elem.attributes = NULL; + node->alt.elem.children = NULL; + node->next = NULL; + return node; +} + +rexmpp_xml_attr_t *rexmpp_xml_attr_new (const char *name, + const char *namespace, + const char *value) +{ + rexmpp_xml_attr_t *attr = malloc(sizeof(rexmpp_xml_attr_t)); + attr->qname.name = strdup(name); + if (namespace != NULL) { + attr->qname.namespace = strdup(namespace); + } else { + attr->qname.namespace = NULL; + } + attr->value = strdup(value); + attr->next = NULL; + return attr; +} + +int rexmpp_xml_add_attr_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace, + const char *value) +{ + if (node == NULL || node->type != REXMPP_XML_ELEMENT) { + return -1; + } + rexmpp_xml_attr_t *attr = + rexmpp_xml_attr_new(name, namespace, value); + attr->next = node->alt.elem.attributes; + node->alt.elem.attributes = attr; + return 0; +} + +int rexmpp_xml_remove_attr_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace) { + if (node == NULL || node->type != REXMPP_XML_ELEMENT) { + return -1; + } + + rexmpp_xml_attr_t **attr, *next_attr; + for (attr = &(node->alt.elem.attributes); *attr != NULL; attr = &((*attr)->next)) { + if (rexmpp_xml_attr_match(*attr, namespace, name)) { + next_attr = (*attr)->next; + rexmpp_xml_attribute_free(*attr); + *attr = next_attr; + return 0; + } + } + return 1; +} + +int rexmpp_xml_add_attr (rexmpp_xml_t *node, + const char *name, + const char *value) +{ + return rexmpp_xml_add_attr_ns(node, name, NULL, value); +} + +int rexmpp_xml_remove_attr (rexmpp_xml_t *node, + const char *name) { + return rexmpp_xml_remove_attr_ns(node, name, NULL); +} + +/* Adds a character, grows the string as needed. */ +static inline char *rexmpp_str_putc (char *str, size_t *len, char c) { + char *ret = str; + if ((*len) % 1024 == 0) { + ret = realloc(str, (*len) + 1024); + if (ret == NULL) { + /* A failure to realloc. */ + if (str != NULL) { + free(str); + } + return NULL; + } + } + ret[*len] = c; + *len = (*len) + 1; + return ret; +} + +static inline +char *rexmpp_str_putc_escaped (char *str, size_t *len, char c) { + char *ret = str; + char buf[7]; + char *esc = buf; + size_t i = 0; + size_t esc_len; + if (c == '<') { + esc = "<"; + } else if (c == '>') { + esc = ">"; + } else if (c == '&') { + esc = "&"; + } else if (c == '\'') { + esc = "'"; + } else if (c == '"') { + esc = """; + } else { + snprintf(esc, 7, "&#%u;", c); + } + esc_len = strlen(esc); + while (i < esc_len) { + ret = rexmpp_str_putc(ret, len, esc[i]); + i++; + } + return ret; +} + +char *rexmpp_xml_print_name (char *str, size_t *len, const char *name) { + char *ret = str; + size_t name_len = strlen(name); + size_t i = 0; + int32_t c = 0; /* matches ICU's UChar32 */ + size_t prev_i = 0, j; + do { + REXMPP_U8_NEXT(name, i, name_len, c); + if (c >= 0) { + if (c == ':' + || (c >= 'A' && c <= 'Z') + || c == '_' + || (c >= 'a' && c <= 'z') + || (c >= 0xC0 && c <= 0xD6) + || (c >= 0xD8 && c <= 0xF6) + || (c >= 0xF8 && c <= 0x2FF) + || (c >= 0x370 && c <= 0x37D) + || (c >= 0x37F && c <= 0x1FFF) + || (c >= 0x200C && c <= 0x200D) + || (c >= 0x2070 && c <= 0x218F) + || (c >= 0x2C00 && c <= 0x2FEF) + || (c >= 0x3001 && c <= 0xD7FF) + || (c >= 0xF900 && c <= 0xFDCF) + || (c >= 0xFDF0 && c <= 0xFFF0) + || (c >= 0x10000 && c <= 0xEFFFF) + || ((i > 0) && + (c == '-' + || c == '.' + || (c >= '0' && c <= '9') + || c == 0xB7 + || (c >= 0x0300 && c <= 0x036F) + || (c >= 0x203F && c <= 0x2040)))) + { + /* Print the allowed characters. */ + for (j = prev_i; j < i; j++) { + ret = rexmpp_str_putc(ret, len, name[j]); + } + } + } else { + /* Skip invalid characters. */ + i++; + } + prev_i = i; + } while (i < name_len); + return ret; +} + +char *rexmpp_xml_print_text (char *str, size_t *len, const char *text) { + char *ret = str; + size_t i = 0; + size_t text_len = strlen(text); + while (i < text_len && ret != NULL) { + char c = text[i]; + if (strchr("<&>'\"", c)) { + /* Escape the few special characters. */ + ret = rexmpp_str_putc_escaped(ret, len, c); + } else { + /* Write others as is. */ + ret = rexmpp_str_putc(ret, len, c); + } + i++; + } + return ret; +} + +char *rexmpp_xml_print_raw (char *str, size_t *len, const char *text) { + char *ret = str; + size_t i = 0; + size_t text_len = strlen(text); + while (i < text_len && ret != NULL) { + char c = text[i]; + ret = rexmpp_str_putc(ret, len, c); + i++; + } + return ret; +} + +static inline char *rexmpp_xml_print_indent (char *str, + size_t *len, + int indent) { + if (indent <= 0) { + return str; + } + int i; + char *ret = str; + for (i = 0; i < indent * 2; i++) { + ret = rexmpp_str_putc(ret, len, ' '); + } + return ret; +} + +char *rexmpp_xml_print (char *str, + size_t *len, + const rexmpp_xml_t *node, + int indent) { + char *ret = str; + if (node->type == REXMPP_XML_TEXT) { + ret = rexmpp_xml_print_text(ret, len, node->alt.text); + } else if (node->type == REXMPP_XML_ELEMENT) { + if (indent > 0) { + ret = rexmpp_str_putc(ret, len, '\n'); + ret = rexmpp_xml_print_indent(ret, len, indent); + } + ret = rexmpp_str_putc(ret, len, '<'); + ret = rexmpp_xml_print_name(ret, len, node->alt.elem.qname.name); + if (node->alt.elem.qname.namespace != NULL) { + ret = rexmpp_xml_print_raw(ret, len, " xmlns=\""); + ret = rexmpp_xml_print_text(ret, len, node->alt.elem.qname.namespace); + ret = rexmpp_str_putc(ret, len, '"'); + } + if (node->alt.elem.attributes != NULL) { + rexmpp_xml_attr_t *attr; + for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) { + ret = rexmpp_str_putc(ret, len, ' '); + /* Ignoring namespaces here for now. */ + ret = rexmpp_xml_print_name(ret, len, attr->qname.name); + ret = rexmpp_xml_print_raw(ret, len, "=\""); + ret = rexmpp_xml_print_text(ret, len, attr->value); + ret = rexmpp_str_putc(ret, len, '"'); + } + } + if (node->alt.elem.children == NULL) { + ret = rexmpp_xml_print_raw(ret, len, "/>"); + } else { + ret = rexmpp_str_putc(ret, len, '>'); + rexmpp_xml_t *child; + int last_child_is_textual = 0; + for (child = rexmpp_xml_children(node); + child != NULL; + child = child->next) + { + ret = rexmpp_xml_print(ret, len, child, + indent > -1 ? indent + 1 : -1); + last_child_is_textual = child->type == REXMPP_XML_TEXT; + } + if (indent >= 0 && ! last_child_is_textual) { + ret = rexmpp_str_putc(ret, len, '\n'); + ret = rexmpp_xml_print_indent(ret, len, indent); + } + ret = rexmpp_xml_print_raw(ret, len, "</"); + ret = rexmpp_xml_print_name(ret, len, node->alt.elem.qname.name); + ret = rexmpp_str_putc(ret, len, '>'); + } + } + return ret; +} + +char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty) { + size_t s_len = 0; + char *s = NULL; + s = rexmpp_xml_print(s, &s_len, node, pretty ? 0 : -1); + s = rexmpp_str_putc(s, &s_len, '\0'); + return s; +} + +rexmpp_xml_t * +rexmpp_xml_add_id (rexmpp_xml_t *node) +{ + char *buf = rexmpp_random_id(); + if (buf == NULL) { + return NULL; + } + rexmpp_xml_add_attr(node, "id", buf); + free(buf); + return node; +} +#endif + +/* These SAX handlers are similar to those from rexmpp.c, and perhaps + can be reused. */ +void rexmpp_xml_parse_sax_characters (struct rexmpp_xml_builder *builder, + const char *ch, + size_t len) +{ + if (builder->current != NULL) { + rexmpp_xml_t *last_node = builder->current->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) { + /* TODO: Would be nice a report an error here. */ + 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 = builder->current->alt.elem.children; + builder->current->alt.elem.children = text_node; + } + } + } +} + +void rexmpp_xml_parse_sax_start_elem_ns (struct rexmpp_xml_builder *builder, + const char *name, + const char *namespace, + rexmpp_xml_attr_t *attributes) +{ + if (builder->current == NULL && builder->root == NULL) { + /* Just started */ + builder->current = rexmpp_xml_new_elem(name, namespace); + builder->root = builder->current; + } else if (builder->current != NULL) { + /* Parsing is in progress */ + rexmpp_xml_t *node = rexmpp_xml_new_elem(name, namespace); + node->next = builder->current->alt.elem.children; + builder->current->alt.elem.children = node; + builder->current = node; + } else { + /* The parsind is over, but we are receiving these events + still. Just free the attribute lists, ignore the rest. */ + rexmpp_xml_attribute_free_list(attributes); + } + builder->current->alt.elem.attributes = attributes; +} + +void rexmpp_xml_parse_sax_end_elem_ns (struct rexmpp_xml_builder *builder) +{ + if (builder->current != builder->root) { + /* Find the parent, set it as current element. */ + rexmpp_xml_t *parent = builder->root; + while (parent->alt.elem.children != builder->current) { + parent = parent->alt.elem.children; + } + builder->current = parent; + } else { + /* Done parsing this element; reverse all the lists of children. */ + builder->current = NULL; + rexmpp_xml_reverse_children(builder->root); + } +} + +struct rexmpp_xml_parser_handlers builder_sax = { + (rexmpp_xml_parser_element_start)rexmpp_xml_parse_sax_start_elem_ns, + (rexmpp_xml_parser_element_end)rexmpp_xml_parse_sax_end_elem_ns, + (rexmpp_xml_parser_characters)rexmpp_xml_parse_sax_characters +}; + +rexmpp_xml_t *rexmpp_xml_parse (const char *str, int str_len) { + struct rexmpp_xml_builder builder = { NULL, NULL }; + rexmpp_xml_parser_ctx_t parser = + rexmpp_xml_parser_new(&builder_sax, &builder); + rexmpp_xml_parser_feed(parser, str, str_len, 1); + rexmpp_xml_parser_free(parser); + if (builder.current != NULL) { + /* The parsing is not complete. */ + rexmpp_xml_free(builder.root); + return NULL; + } + return builder.root; +} + +rexmpp_xml_t *rexmpp_xml_read_fd (int fd) { + struct rexmpp_xml_builder builder = { NULL, NULL }; + rexmpp_xml_parser_ctx_t parser = + rexmpp_xml_parser_new(&builder_sax, &builder); + if (parser == NULL) { + return NULL; + } + + char buf[4096]; + ssize_t buf_len = 0; + do { + buf_len = read(fd, buf, 4096); + if (buf_len > 0) { + rexmpp_xml_parser_feed(parser, buf, buf_len, 0); + } + } while (buf_len > 0 && + ! (builder.root != NULL && builder.current == NULL) ); + + rexmpp_xml_parser_free(parser); + + if (builder.current != NULL) { + /* The parsing is not complete. */ + rexmpp_xml_free(builder.root); + return NULL; + } + + return builder.root; +} + +rexmpp_xml_t *rexmpp_xml_read_file (const char *path) { + int fd = open(path, O_RDONLY); + if (fd < 0) { + return NULL; + } + rexmpp_xml_t *node = rexmpp_xml_read_fd(fd); + close(fd); + return node; +} + +#ifndef USE_RUST +int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node) { + FILE *fd = fopen(path, "w"); + if (fd == NULL) { + return -1; + } + char *serialized = rexmpp_xml_serialize(node, 1); + fputs("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n", fd); + fputs(serialized, fd); + fclose(fd); + return 0; +} + +unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node) { + unsigned int i = 0; + for (i = 0; node != NULL; i++) { + node = node->next; + } + return i; +} + +int rexmpp_xml_match (rexmpp_xml_t *node, + const char *namespace, + const char *name) +{ + if (node == NULL) { + return 0; + } + if (node->type != REXMPP_XML_ELEMENT) { + return 0; + } + if (name != NULL) { + if (strcmp(name, node->alt.elem.qname.name) != 0) { + return 0; + } + } + if (namespace != NULL) { + if (node->alt.elem.qname.namespace == NULL && + strcmp(namespace, "jabber:client") != 0) { + return 0; + } else if (node->alt.elem.qname.namespace != NULL) { + if (strcmp(namespace, node->alt.elem.qname.namespace) != 0) { + return 0; + } + } + } + return 1; +} + +int rexmpp_xml_attr_match (rexmpp_xml_attr_t *attr, + const char *namespace, + const char *name) +{ + if (attr == NULL) { + return 0; + } + if (name != NULL) { + if (strcmp(name, attr->qname.name) != 0) { + return 0; + } + } + if (namespace != NULL) { + if (attr->qname.namespace == NULL && + strcmp(namespace, "jabber:client") != 0) { + return 0; + } else if (strcmp(namespace, attr->qname.namespace) != 0) { + return 0; + } + } + return 1; +} + +int rexmpp_xml_is_stanza (rexmpp_xml_t *node) { + return rexmpp_xml_match(node, "jabber:client", "message") || + rexmpp_xml_match(node, "jabber:client", "iq") || + rexmpp_xml_match(node, "jabber:client", "presence"); +} + +rexmpp_xml_t *rexmpp_xml_error (const char *type, const char *condition) { + rexmpp_xml_t * error = rexmpp_xml_new_elem("error", NULL); + rexmpp_xml_add_attr(error, "type", type); + rexmpp_xml_t * cond = + rexmpp_xml_new_elem(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); + rexmpp_xml_add_child(error, cond); + return error; +} + +rexmpp_xml_attr_t * +rexmpp_xml_find_attr (rexmpp_xml_t *node, + const char *name, + const char *namespace) +{ + if (node == NULL || node->type != REXMPP_XML_ELEMENT) { + return NULL; + } + rexmpp_xml_attr_t *attr; + for (attr = node->alt.elem.attributes; attr != NULL; attr = attr->next) { + if (rexmpp_xml_attr_match(attr, namespace, name)) { + return attr; + } + } + return NULL; +} + +const char *rexmpp_xml_find_attr_val_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace) { + rexmpp_xml_attr_t *attr = rexmpp_xml_find_attr(node, name, namespace); + if (attr != NULL) { + return attr->value; + } + return NULL; +} + +const char *rexmpp_xml_find_attr_val (rexmpp_xml_t *node, + const char *name) { + return rexmpp_xml_find_attr_val_ns(node, name, NULL); +} + +rexmpp_xml_t * +rexmpp_xml_find_child (rexmpp_xml_t *node, + const char *namespace, + const char *name) +{ + if (node == NULL || node->type != REXMPP_XML_ELEMENT) { + return NULL; + } + rexmpp_xml_t *child; + for (child = node->alt.elem.children; child != NULL; child = child->next) { + if (rexmpp_xml_match(child, namespace, name)) { + return child; + } + } + return NULL; +} + +int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2) { + /* Just serialize and compare strings for now: awkward, but + simple. */ + char *n1str = rexmpp_xml_serialize(n1, 0); + char *n2str = rexmpp_xml_serialize(n2, 0); + int eq = (strcmp(n1str, n2str) == 0); + free(n1str); + free(n2str); + return eq; +} + +rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node) { + if (node != NULL && node->type == REXMPP_XML_ELEMENT) { + return node->alt.elem.children; + } + return NULL; +} + +rexmpp_xml_t *rexmpp_xml_first_elem_child (rexmpp_xml_t *node) { + rexmpp_xml_t *child; + for (child = rexmpp_xml_children(node); child != NULL; child = child->next) { + if (child->type == REXMPP_XML_ELEMENT) { + return child; + } + } + return NULL; +} + +rexmpp_xml_t *rexmpp_xml_next_elem_sibling (rexmpp_xml_t *node) { + if (node == NULL) { + return NULL; + } + rexmpp_xml_t *sibling; + for (sibling = node->next; sibling != NULL; sibling = sibling->next) { + if (sibling->type == REXMPP_XML_ELEMENT) { + return sibling; + } + } + return NULL; +} + +char *rexmpp_xml_text (rexmpp_xml_t *node) { + if (node != NULL && node->type == REXMPP_XML_TEXT) { + return node->alt.text; + } + return NULL; +} + +char *rexmpp_xml_text_child (rexmpp_xml_t *node) { + return rexmpp_xml_text(rexmpp_xml_children(node)); +} + +rexmpp_xml_t *rexmpp_xml_reverse_list (rexmpp_xml_t *node) { + rexmpp_xml_t *next, *prev = NULL; + while (node != NULL) { + next = node->next; + node->next = prev; + prev = node; + node = next; + } + return prev; +} + +void rexmpp_xml_reverse_children (rexmpp_xml_t *node) { + if (node == NULL || node->type != REXMPP_XML_ELEMENT) { + return; + } + node->alt.elem.children = rexmpp_xml_reverse_list(node->alt.elem.children); + rexmpp_xml_t *cur; + for (cur = node->alt.elem.children; cur != NULL; cur = cur->next) { + if (cur->type == REXMPP_XML_ELEMENT && cur->alt.elem.children != NULL) { + rexmpp_xml_reverse_children(cur); + } + } +} + +#endif diff --git a/src/rexmpp_xml.h b/src/rexmpp_xml.h new file mode 100644 index 0000000..38142ae --- /dev/null +++ b/src/rexmpp_xml.h @@ -0,0 +1,269 @@ +/** + @file rexmpp_xml.h + @brief XML structures and functions for rexmpp + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_XML_H +#define REXMPP_XML_H + +#include <stdio.h> + +typedef struct rexmpp_xml_qname rexmpp_xml_qname_t; +typedef struct rexmpp_xml_attribute rexmpp_xml_attr_t; +typedef struct rexmpp_xml_node rexmpp_xml_t; + +struct rexmpp_xml_qname { + char *name; + char *namespace; +}; + +struct rexmpp_xml_attribute { + rexmpp_xml_qname_t qname; + char *value; + rexmpp_xml_attr_t *next; +}; + +enum rexmpp_xml_node_type { + REXMPP_XML_ELEMENT, + REXMPP_XML_TEXT +}; + +typedef enum rexmpp_xml_node_type rexmpp_xml_node_type_t; + +struct rexmpp_xml_node { + rexmpp_xml_node_type_t type; + union { + struct { + rexmpp_xml_qname_t qname; + rexmpp_xml_attr_t *attributes; + rexmpp_xml_t *children; + } elem; + char *text; + } alt; + rexmpp_xml_t *next; +}; + +struct rexmpp_xml_builder { + rexmpp_xml_t *current; + rexmpp_xml_t *root; +}; + +void rexmpp_xml_qname_free (rexmpp_xml_qname_t *qname); +void rexmpp_xml_attribute_free (rexmpp_xml_attr_t *attr); +void rexmpp_xml_attribute_free_list (rexmpp_xml_attr_t *attr); + +/** + @brief Frees a single XML node. Does not free its siblings. +*/ +void rexmpp_xml_free (rexmpp_xml_t *node); + +/** + @brief Frees an XML node and its siblings. +*/ +void rexmpp_xml_free_list (rexmpp_xml_t *node); + +/** + @brief Clones a single XML node, without its siblings. +*/ +rexmpp_xml_t *rexmpp_xml_clone (rexmpp_xml_t *node); + +/** + @brief Clones an XML node, together with its siblings. +*/ +rexmpp_xml_t *rexmpp_xml_clone_list (rexmpp_xml_t *node); + +/** + @brief Creates a textual ::rexmpp_xml_t XML node (with type = + ::REXMPP_XML_TEXT). +*/ +rexmpp_xml_t *rexmpp_xml_new_text (const char *str); + +/** + @brief Creates a textual ::rexmpp_xml_t XML node (with type = + ::REXMPP_XML_TEXT). +*/ +rexmpp_xml_t *rexmpp_xml_new_text_len (const char *str, size_t len); + +/** + @brief Creates an element ::rexmpp_xml_t XML node (with type = + ::REXMPP_XML_ELEMENT). +*/ +rexmpp_xml_t *rexmpp_xml_new_elem (const char *name, + const char *namespace); + +/** + @brief Adds a child node. +*/ +void rexmpp_xml_add_child (rexmpp_xml_t *node, + rexmpp_xml_t *child); + +/** + @brief Creates a text node, and adds it as a child. +*/ +int rexmpp_xml_add_text (rexmpp_xml_t *node, + const char *str); + +/** + @brief Creates a text node, and adds it as a child. +*/ +int rexmpp_xml_add_text_len (rexmpp_xml_t *node, + const char *str, + size_t len); + +rexmpp_xml_attr_t *rexmpp_xml_attr_new (const char *name, + const char *namespace, + const char *value); + +int rexmpp_xml_add_attr (rexmpp_xml_t *node, + const char *name, + const char *value); + +int rexmpp_xml_remove_attr_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace); + +int rexmpp_xml_remove_attr (rexmpp_xml_t *node, + const char *name); + +int rexmpp_xml_add_attr_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace, + const char *value); + +/** + @brief Adds an "id" attribute to an XML stanza. + @param[in,out] s ::rexmpp + @param[in] node A pointer to an XML stanza. + @returns The same pointer as on input, for more convenient + composition. +*/ +rexmpp_xml_t * +rexmpp_xml_add_id (rexmpp_xml_t *node); + +/** + @brief A helper function for XML serialisation. + @param[in] node An XML node. + @returns A string (must be freed by the caller). +*/ +char *rexmpp_xml_serialize (const rexmpp_xml_t *node, int pretty); + +/** + @brief Count the number of siblings after a given node. +*/ +unsigned int rexmpp_xml_siblings_count (rexmpp_xml_t *node); + +/** + @brief Compares the node's name and namespace to given ones. +*/ +int rexmpp_xml_match (rexmpp_xml_t *node, + const char *namespace, + const char *name); + +int rexmpp_xml_is_stanza (rexmpp_xml_t *node); + +/** + @brief Compose an 'error' element. +*/ +rexmpp_xml_t *rexmpp_xml_error (const char *type, const char *condition); + +/** + @brief Matches an XML node against a namespace and an element name. + @param[in] node An XML node to match. + @param[in] namespace An XML namespace. Can be NULL (matches + anything), and it is assumed that the default namespace is + "jabber:client" (so if it is "jabber:client" and an element doesn't + have a namespace defined, this function counts that as a match). + @param[in] name Element name. Can be NULL (matches anything). + @returns 1 on a successful match, 0 otherwise. +*/ +int rexmpp_xml_attr_match (rexmpp_xml_attr_t *attr, + const char *namespace, + const char *name); + +rexmpp_xml_attr_t *rexmpp_xml_find_attr (rexmpp_xml_t *node, + const char *name, + const char *namespace); + +const char *rexmpp_xml_find_attr_val_ns (rexmpp_xml_t *node, + const char *name, + const char *namespace); + +const char *rexmpp_xml_find_attr_val (rexmpp_xml_t *node, + const char *name); + +/** + @brief Finds a child element of an XML node, which matches the + given namespace and name. + @param[in] node The node containing child nodes. + @param[in] namespace The namespace to look for. + @param[in] name The element name to look for. + @returns A pointer to the first matching child node, or NULL if no + matching child elements found. +*/ +rexmpp_xml_t *rexmpp_xml_find_child (rexmpp_xml_t *node, + const char *namespace, + const char *name); + +rexmpp_xml_t *rexmpp_xml_children (const rexmpp_xml_t *node); + +char *rexmpp_xml_text (rexmpp_xml_t *node); + +char *rexmpp_xml_text_child (rexmpp_xml_t *node); + +rexmpp_xml_t *rexmpp_xml_first_elem_child (rexmpp_xml_t *node); + +rexmpp_xml_t *rexmpp_xml_next_elem_sibling (rexmpp_xml_t *node); + +/** + @brief Compares two XML elements. +*/ +int rexmpp_xml_eq (rexmpp_xml_t *n1, rexmpp_xml_t *n2); + +/** + @brief A helper function for XML parsing. + @param[in] str A string to parse. + @param[in] str_len String length. + @returns Parsed XML, or NULL on failure. +*/ +rexmpp_xml_t *rexmpp_xml_parse (const char *str, int str_len); + +/** + @brief Reads XML from a file stream, reading the stream line by + line. + @param[in] fd A file descriptor + @returns Parsed XML, or NULL on failure. +*/ +rexmpp_xml_t *rexmpp_xml_read_fd (int fd); + +/** + @brief Reads XML from a file + @param[in] path A file path + @returns Parsed XML, or NULL on failure. +*/ +rexmpp_xml_t *rexmpp_xml_read_file (const char *path); + +/** + @brief Writes XML into a file + @param[in] path A file path + @param[in] node XML to write + @returns 0 on success, -1 on failure. +*/ +int rexmpp_xml_write_file (const char *path, rexmpp_xml_t* node); + +/** + @brief Reverses a linked list of XML nodes + @param[in,out] node The head of the list to reverse + @returns The new head of the list +*/ +rexmpp_xml_t *rexmpp_xml_reverse_list (rexmpp_xml_t *node); + +/** + @brief Recursively reverses children of an XML element + @param[in,out] node The root XML element +*/ +void rexmpp_xml_reverse_children (rexmpp_xml_t *node); + +#endif diff --git a/src/rexmpp_xml.rs b/src/rexmpp_xml.rs new file mode 100644 index 0000000..f0d292a --- /dev/null +++ b/src/rexmpp_xml.rs @@ -0,0 +1,1040 @@ +extern crate libc; +use libc::{strdup, strndup, free, strcmp}; +use std::os::raw::{c_char, c_int, c_void, c_uint}; +use std::ptr; +use std::ffi::{CStr, CString}; +use std::clone::Clone; +use std::fs::File; +use std::io::Write; + +use super::{rexmpp}; +use super::{rexmpp_random}; + +// extern { +// fn rexmpp_xml_parse (str: *mut c_char, str_len: c_int) -> *mut RexmppXML; +// } + +#[repr(C)] +pub struct RexmppXMLQName { + pub name: *mut c_char, + pub namespace: *mut c_char +} + +impl Copy for RexmppXMLQName { } + +impl Clone for RexmppXMLQName { + fn clone(&self) -> RexmppXMLQName { + RexmppXMLQName { + name: unsafe { strdup(self.name) }, + namespace: if self.namespace != ptr::null_mut() { + unsafe { strdup(self.namespace) } + } else { + ptr::null_mut() + } + } + } +} + +#[repr(C)] +pub struct RexmppXMLAttribute { + pub qname: RexmppXMLQName, + pub value: *mut c_char, + pub next: *mut RexmppXMLAttribute +} + +impl Copy for RexmppXMLAttribute { } + +impl Clone for RexmppXMLAttribute { + fn clone(&self) -> RexmppXMLAttribute { + RexmppXMLAttribute { + qname: Clone::clone(&self.qname), + value: unsafe { strdup(self.value) }, + next: ptr::null_mut() + } + } +} + +#[derive(Copy, Clone)] +#[derive(PartialEq)] +#[repr(C)] +pub enum NodeType { + Element, + Text +} + +#[repr(C)] +pub struct RexmppXMLAltElem { + pub qname: RexmppXMLQName, + pub attributes: *mut RexmppXMLAttribute, + pub children: *mut RexmppXML +} + +impl Copy for RexmppXMLAltElem { } + +impl Clone for RexmppXMLAltElem { + fn clone(&self) -> RexmppXMLAltElem { + unsafe { + let mut ret = RexmppXMLAltElem { + qname: Clone::clone(&self.qname), + attributes: ptr::null_mut(), + children: ptr::null_mut() + }; + let mut old_attr_ptr = self.attributes; + let mut next_attr_ptr_ptr : *mut *mut RexmppXMLAttribute = &mut ret.attributes; + loop { + match old_attr_ptr.as_mut() { + None => break, + Some(old_attr) => { + let new_attr_ptr = rexmpp_xml_attr_new(old_attr.qname.name, + old_attr.qname.namespace, + old_attr.value); + next_attr_ptr_ptr.write(new_attr_ptr); + next_attr_ptr_ptr = &mut ((*new_attr_ptr).next); + old_attr_ptr = old_attr.next; + } + } + } + ret.children = rexmpp_xml_clone_list(self.children); + return ret; + } + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub enum RexmppXMLAlt { + Elem(RexmppXMLAltElem), + Text(*mut c_char) +} + +#[repr(C)] +pub struct RexmppXML { + pub alt: RexmppXMLAlt, + pub next: *mut RexmppXML +} + +impl Copy for RexmppXML { } + +impl Clone for RexmppXML { + fn clone(&self) -> RexmppXML { + RexmppXML { + alt: match self.alt { + RexmppXMLAlt::Text(text) => + RexmppXMLAlt::Text(unsafe { strdup(text) }), + RexmppXMLAlt::Elem(e) => + RexmppXMLAlt::Elem(Clone::clone(& e)) + }, + next: ptr::null_mut() + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_qname_free (qname_ptr: *mut RexmppXMLQName) { + match unsafe { qname_ptr.as_mut() } { + None => return, + Some(qname) => { + if qname.name != ptr::null_mut() { + unsafe { free(qname.name as *mut c_void) }; + qname.name = ptr::null_mut(); + } + if qname.namespace != ptr::null_mut() { + unsafe { free(qname.namespace as *mut c_void) }; + qname.namespace = ptr::null_mut(); + } + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_attribute_free (attr_ptr: *mut RexmppXMLAttribute) { + if attr_ptr == ptr::null_mut() { + return; + } + let mut attr : RexmppXMLAttribute = unsafe { *Box::from_raw(attr_ptr) }; + rexmpp_xml_qname_free(&mut (attr.qname)); + if attr.value != ptr::null_mut() { + unsafe { free(attr.value as *mut c_void) } + attr.value = ptr::null_mut(); + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_attribute_free_list (mut attr_ptr: *mut RexmppXMLAttribute) { + let mut next; + while attr_ptr != ptr::null_mut() { + next = unsafe { (*attr_ptr).next }; + rexmpp_xml_attribute_free(attr_ptr); + attr_ptr = next; + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_free (node_ptr: *mut RexmppXML) { + if node_ptr == ptr::null_mut() { + return; + } + let mut node : RexmppXML = unsafe { *Box::from_raw(node_ptr) }; + unsafe { + match node.alt { + RexmppXMLAlt::Text(mut t) => { + free(t as *mut c_void); + t = ptr::null_mut(); + }, + RexmppXMLAlt::Elem(mut element) => { + rexmpp_xml_qname_free(&mut (element.qname)); + rexmpp_xml_attribute_free_list(element.attributes); + rexmpp_xml_free_list(element.children); + } + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_free_list (mut node_ptr: *mut RexmppXML) { + let mut next; + while node_ptr != ptr::null_mut() { + next = unsafe { (*node_ptr).next }; + rexmpp_xml_free(node_ptr); + node_ptr = next; + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_clone (node_ptr: *mut RexmppXML) -> *mut RexmppXML { + if node_ptr == ptr::null_mut() { + return ptr::null_mut(); + } + return Box::into_raw(Box::new(Clone::clone(& unsafe { *node_ptr }))); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_clone_list (mut node_ptr: *mut RexmppXML) -> *mut RexmppXML { + if node_ptr == ptr::null_mut() { + return ptr::null_mut(); + } + let first_ptr = rexmpp_xml_clone(node_ptr); + let mut last_ptr = first_ptr; + node_ptr = unsafe { (*node_ptr).next }; + while node_ptr != ptr::null_mut() { + unsafe { (*last_ptr).next = rexmpp_xml_clone(node_ptr) }; + last_ptr = unsafe { (*last_ptr).next }; + node_ptr = unsafe { (*node_ptr).next }; + } + return first_ptr; +} + + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_new_text (str: *const c_char) -> *mut RexmppXML { + let node = RexmppXML { + alt: RexmppXMLAlt::Text( unsafe { strdup(str) } ), + next: ptr::null_mut() + }; + let b = Box::new(node); + return Box::into_raw(b); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_new_text_len (str: *const c_char, len: usize) -> *mut RexmppXML { + let node = RexmppXML { + alt: RexmppXMLAlt::Text( unsafe { strndup(str, len) } ), + next: ptr::null_mut() + }; + let b = Box::new(node); + return Box::into_raw(b); +} + +#[no_mangle] +pub extern "C" fn rexmpp_xml_add_child (node: *mut RexmppXML, + child: *mut RexmppXML) -> () { + // It is important to wrap everything in "unsafe" here; somehow + // the enum fields are not mutated otherwise. + unsafe { + match (*node).alt { + RexmppXMLAlt::Elem(ref mut elem) => { + let mut last_ptr : &mut *mut RexmppXML = + &mut ((*elem).children); + while *last_ptr != ptr::null_mut() { + last_ptr = &mut ((*(* last_ptr)).next); + } + *last_ptr = child; + }, + _ => () + } + } +} + +#[no_mangle] +pub extern "C" fn rexmpp_xml_add_text (node: *mut RexmppXML, + str: *const c_char) -> c_int { + let text_node : *mut RexmppXML = rexmpp_xml_new_text(str); + if text_node != ptr::null_mut() { + rexmpp_xml_add_child(node, text_node); + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rexmpp_xml_add_text_len (node: *mut RexmppXML, + str: *const c_char, + len: usize) -> c_int { + let text_node : *mut RexmppXML = rexmpp_xml_new_text_len(str, len); + if text_node != ptr::null_mut() { + rexmpp_xml_add_child(node, text_node); + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_new_elem (name: *const c_char, + namespace: *const c_char) -> *mut RexmppXML { + let node = RexmppXML { + alt: RexmppXMLAlt::Elem ( + RexmppXMLAltElem { + qname: RexmppXMLQName { + name: unsafe { strdup(name) }, + namespace: if namespace == ptr::null_mut() { + ptr::null_mut() + } else { + unsafe { strdup(namespace) } + } + }, + attributes: ptr::null_mut(), + children: ptr::null_mut() + } + ), + next: ptr::null_mut() + }; + let b = Box::new(node); + return Box::into_raw(b); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_attr_new (name: *const c_char, + namespace: *const c_char, + value: *const c_char) -> *mut RexmppXMLAttribute { + let node = RexmppXMLAttribute { + qname: RexmppXMLQName { + name: unsafe { strdup(name) }, + namespace: if namespace == ptr::null_mut() { + ptr::null_mut() + } else { + unsafe { strdup(namespace) } + } + }, + value: unsafe { strdup(value) }, + next: ptr::null_mut() + }; + return Box::into_raw(Box::new(node)); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_add_attr_ns (node: *mut RexmppXML, + name: *const c_char, + namespace: *const c_char, + value: *const c_char) -> c_int { + if node == ptr::null_mut() { + return -1; + } + // Wrapping everything into "unsafe", otherwise enum fields are + // not mutated. + unsafe { + match(*node).alt { + RexmppXMLAlt::Elem(ref mut elem) => { + let attr = rexmpp_xml_attr_new(name, namespace, value); + (*attr).next = (*elem).attributes; + (*elem).attributes = attr; + 0 + }, + _ => -1 + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_remove_attr_ns (node: *mut RexmppXML, + name: *const c_char, + namespace: *const c_char) -> c_int { + if node == ptr::null_mut() { + return -1; + } + // Wrapping everything into "unsafe", otherwise enum fields are + // not mutated. + unsafe { + match (*node).alt { + RexmppXMLAlt::Elem(ref mut elem) => { + let mut attr_ptr_ptr: *mut *mut RexmppXMLAttribute = + &mut (*elem).attributes; + while *attr_ptr_ptr != ptr::null_mut() { + if rexmpp_xml_attr_match(*attr_ptr_ptr, + namespace, name) > 0 { + let next_attr_ptr : *mut RexmppXMLAttribute = + (**attr_ptr_ptr).next; + rexmpp_xml_attribute_free(*attr_ptr_ptr); + *attr_ptr_ptr = next_attr_ptr; + return 0; + } + attr_ptr_ptr = &mut (**attr_ptr_ptr).next; + } + 1 + }, + _ => -1, + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_add_attr (node: *mut RexmppXML, + name: *const c_char, + value: *const c_char) -> c_int { + rexmpp_xml_add_attr_ns(node, name, ptr::null_mut(), value) +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_remove_attr (node: *mut RexmppXML, + name: *const c_char) -> c_int { + rexmpp_xml_remove_attr_ns(node, name, ptr::null_mut()) +} + + +fn rexmpp_xml_push_escaped (c: char, s: &mut String) { + if c == '<' { + s.push_str("<") + } else if c == '>' { + s.push_str(">") + } else if c == '&' { + s.push_str("&") + } else if c == '\'' { + s.push_str("'") + } else if c == '"' { + s.push_str(""") + } else { + s.push_str(format!("&#{};", u32::from(c)).as_str()); + }; +} + +fn rexmpp_xml_print_text (c: char, s: &mut String) { + if "<&>'\"".chars().any(|sc| sc == c) { + rexmpp_xml_push_escaped(c, s); + } else { + s.push(c); + } +} + +fn rexmpp_xml_print_name (i: usize, c: char, s: &mut String) { + if c == ':' + || (c >= 'A' && c <= 'Z') + || c == '_' + || (c >= 'a' && c <= 'z') + || (c >= '\u{C0}' && c <= '\u{D6}') + || (c >= '\u{D8}' && c <= '\u{F6}') + || (c >= '\u{F8}' && c <= '\u{2FF}') + || (c >= '\u{370}' && c <= '\u{37D}') + || (c >= '\u{37F}' && c <= '\u{1FFF}') + || (c >= '\u{200C}' && c <= '\u{200D}') + || (c >= '\u{2070}' && c <= '\u{218F}') + || (c >= '\u{2C00}' && c <= '\u{2FEF}') + || (c >= '\u{3001}' && c <= '\u{D7FF}') + || (c >= '\u{F900}' && c <= '\u{FDCF}') + || (c >= '\u{FDF0}' && c <= '\u{FFF0}') + || (c >= '\u{10000}' && c <= '\u{EFFFF}') + || ((i > 0) && + (c == '-' + || c == '.' + || (c >= '0' && c <= '9') + || c == '\u{B7}' + || (c >= '\u{0300}' && c <= '\u{036F}') + || (c >= '\u{203F}' && c <= '\u{2040}'))) + { + // Print the allowed characters. + s.push(c); + } +} + +fn rexmpp_xml_print_indent (indent: i32, s: &mut String) { + let mut i = 0; + while i < indent { + s.push_str(" "); + i = i + 1; + } +} + +fn rexmpp_xml_print (node_ptr: *const RexmppXML, + ret: &mut String, + indent: i32) + -> () +{ + unsafe { + let node : RexmppXML = *node_ptr; + match node { + RexmppXML { alt : RexmppXMLAlt::Text (text_ptr), + next: _} => { + let text_cstr : &CStr = CStr::from_ptr(text_ptr); + let text_str : String = + String::from_utf8_lossy(text_cstr.to_bytes()) + .to_string(); + // let mut escaped = String::with_capacity(text_str.capacity()); + text_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + }, + RexmppXML { alt : RexmppXMLAlt::Elem (element), + next: _} => { + // let mut ret = String::with_capacity(1024); + let name_cstr : &CStr = + CStr::from_ptr(element.qname.name); + let name_str : String = + String::from_utf8_lossy(name_cstr.to_bytes()) + .to_string(); + if indent > 0 { + ret.push('\n'); + rexmpp_xml_print_indent(indent, ret); + } + ret.push('<'); + name_str.char_indices(). + for_each(|(i, c)| rexmpp_xml_print_name(i, c, ret)); + if element.qname.namespace != ptr::null_mut() { + let namespace_cstr : &CStr = + CStr::from_ptr(element.qname.namespace); + let namespace_str : String = + String::from_utf8_lossy(namespace_cstr.to_bytes()) + .to_string(); + ret.push_str(" xmlns=\""); + namespace_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + ret.push('"'); + } + if element.attributes != ptr::null_mut() { + let mut attr_ptr : *mut RexmppXMLAttribute = + element.attributes; + while attr_ptr != ptr::null_mut() { + let attr : RexmppXMLAttribute = *attr_ptr; + let attr_name_cstr = + CStr::from_ptr(attr.qname.name); + let attr_name_str = + String::from_utf8_lossy(attr_name_cstr.to_bytes()) + .to_string(); + // TODO: handle attribute namespaces someday. + let attr_value_cstr = + CStr::from_ptr(attr.value); + let attr_value_str = + String::from_utf8_lossy(attr_value_cstr.to_bytes()) + .to_string(); + ret.push(' '); + attr_name_str.char_indices(). + for_each(|(i, c)| + rexmpp_xml_print_name(i, c, ret)); + ret.push_str("=\""); + attr_value_str.chars(). + for_each(|c| rexmpp_xml_print_text(c, ret)); + ret.push('"'); + attr_ptr = (*attr_ptr).next; + } + } + if element.children == ptr::null_mut() { + ret.push_str("/>"); + } else { + ret.push('>'); + let mut child = rexmpp_xml_children(node_ptr); + let mut last_child_is_textual = false; + while child != ptr::null_mut() { + rexmpp_xml_print(child, ret, + if indent > -1 { indent + 1 } + else { -1 } ); + last_child_is_textual = + matches!((*child).alt, RexmppXMLAlt::Text(_)); + child = (*child).next; + } + if indent > 0 && ! last_child_is_textual { + ret.push('\n'); + rexmpp_xml_print_indent(indent, ret); + } + ret.push_str("</"); + name_str.char_indices(). + for_each(|(i, c)| + rexmpp_xml_print_name(i, c, ret)); + ret.push('>'); + } + } + } + } +} + +fn rexmpp_xml_serialize_str (node_ptr: *const RexmppXML, + pretty: bool) + -> String +{ + let mut out = String::with_capacity(4096); + rexmpp_xml_print(node_ptr, &mut out, if pretty { 0 } else { -1 }); + return out; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_serialize (node_ptr: *const RexmppXML, + pretty: bool) + -> *mut c_char +{ + let out = rexmpp_xml_serialize_str(node_ptr, pretty); + match CString::new(out) { + Ok(cstr) => unsafe { strdup(cstr.as_ptr()) }, + Err(_) => ptr::null_mut() + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_add_id (node: *mut RexmppXML) + -> *mut RexmppXML +{ + match CString::new("id") { + Err(_) => return ptr::null_mut(), + Ok(id_cstr) => { + let buf = unsafe { rexmpp_random::rexmpp_random_id() }; + if buf == ptr::null_mut() { + return ptr::null_mut(); + } + rexmpp_xml_add_attr(node, id_cstr.as_ptr(), buf); + unsafe { free(buf as *mut c_void) }; + return node; + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_write_file (path: *const c_char, + node: *const RexmppXML) + -> c_int +{ + let path_cstr : &CStr = unsafe { CStr::from_ptr(path) }; + let path_str : String = + String::from_utf8_lossy(path_cstr.to_bytes()) + .to_string(); + match File::create(path_str) { + Ok(mut fd) => { + fd.write_all(b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); + fd.write_all(rexmpp_xml_serialize_str(node, false).as_bytes()); + }, + Err(_) => { return -1; } + } + return 0; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_siblings_count (mut node: *const RexmppXML) -> c_uint { + let mut i : c_uint = 0; + while node != ptr::null() { + node = unsafe { (*node).next }; + i = i + 1; + } + return i; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_match (node: *const RexmppXML, + namespace: *const c_char, + name: *const c_char) -> c_int { + if node == ptr::null_mut() { + return 0; + } + match unsafe { (*node).alt } { + RexmppXMLAlt::Text(_) => return 0, + RexmppXMLAlt::Elem(elem) => { + if name != ptr::null_mut() { + let name_cstr : &CStr = unsafe { CStr::from_ptr(name) }; + let elem_name_cstr : &CStr = + unsafe { CStr::from_ptr(elem.qname.name) }; + if name_cstr != elem_name_cstr { + return 0; + } + } + if namespace != ptr::null_mut() { + let namespace_cstr : &CStr = unsafe { CStr::from_ptr(namespace) }; + if unsafe { elem.qname.namespace } == ptr::null_mut() { + match CStr::to_str(namespace_cstr) { + Ok(namespace_str) => if namespace_str == "jabber:client" { + return 1; + }, + Err(_) => return 0 + } + return 0; + } + let elem_namespace_cstr : &CStr = + unsafe { CStr::from_ptr(elem.qname.namespace) }; + if namespace_cstr != elem_namespace_cstr { + return 0; + } + } + } + } + return 1; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_attr_match (attr: *const RexmppXMLAttribute, + namespace: *const c_char, + name: *const c_char) -> c_int { + if attr == ptr::null() { + return 0; + } + if name != ptr::null() { + let name_cstr : &CStr = unsafe { CStr::from_ptr(name) }; + let attr_name_cstr : &CStr = unsafe { CStr::from_ptr((*attr).qname.name) }; + if name_cstr != attr_name_cstr { + return 0; + } + } + if namespace != ptr::null() { + let namespace_cstr : &CStr = unsafe { CStr::from_ptr(namespace) }; + if unsafe { (*attr).qname.namespace } == ptr::null_mut() { + match CStr::to_str(namespace_cstr) { + Ok(namespace_str) => if namespace_str != "jabber:client" { + return 0; + }, + Err(_) => return 0 + } + } else { + let attr_namespace_cstr : &CStr = + unsafe { CStr::from_ptr((*attr).qname.namespace) }; + if namespace_cstr != attr_namespace_cstr { + return 0; + } + } + } + return 1; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_is_stanza (node: *const RexmppXML) -> c_int { + if rexmpp_xml_match(node, + CString::new("jabber:client").expect("CString::new failed").as_ptr(), + CString::new("message").expect("CString::new failed").as_ptr()) == 1 + || rexmpp_xml_match(node, + CString::new("jabber:client").expect("CString::new failed").as_ptr(), + CString::new("iq").expect("CString::new failed").as_ptr()) == 1 + || rexmpp_xml_match(node, + CString::new("jabber:client").expect("CString::new failed").as_ptr(), + CString::new("presence").expect("CString::new failed").as_ptr()) == 1 + { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_error (error_type: *const c_char, condition: *const c_char) + -> *mut RexmppXML { + let error : *mut RexmppXML = + rexmpp_xml_new_elem(CString::new("error") + .expect("CString::new failed") + .as_ptr(), + ptr::null_mut()); + rexmpp_xml_add_attr(error, + CString::new("type") + .expect("CString::new failed") + .as_ptr(), + error_type); + let cond = + rexmpp_xml_new_elem(condition, + CString::new("urn:ietf:params:xml:ns:xmpp-stanzas") + .expect("CString::new failed") + .as_ptr()); + rexmpp_xml_add_child(error, cond); + return error; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_find_attr (node: *mut RexmppXML, + name: *const c_char, + namespace: *const c_char) + -> *mut RexmppXMLAttribute { + if node == ptr::null_mut() { + return ptr::null_mut(); + } + match unsafe { (*node).alt } { + RexmppXMLAlt::Elem(elem) => { + let mut attr_ptr : *mut RexmppXMLAttribute = + unsafe { elem.attributes }; + while attr_ptr != ptr::null_mut() { + if rexmpp_xml_attr_match(attr_ptr, namespace, name) > 0 { + return attr_ptr; + } + unsafe { attr_ptr = (*attr_ptr).next }; + } + return ptr::null_mut(); + }, + _ => return ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_find_attr_val_ns (node: *mut RexmppXML, + name: *const c_char, + namespace: *const c_char) + -> *const c_char { + let attr : *mut RexmppXMLAttribute = + rexmpp_xml_find_attr(node, name, namespace); + if attr != ptr::null_mut() { + return unsafe { (*attr).value }; + } + return ptr::null_mut(); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_find_attr_val (node: *mut RexmppXML, + name: *const c_char) + -> *const c_char { + rexmpp_xml_find_attr_val_ns(node, name, ptr::null_mut()) +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_find_child (node: *mut RexmppXML, + namespace: *const c_char, + name: *const c_char) + -> *mut RexmppXML { + if node == ptr::null_mut() { + return ptr::null_mut(); + } + match unsafe { (*node).alt } { + RexmppXMLAlt::Elem(elem) => { + let mut child: *mut RexmppXML = unsafe { elem.children }; + while child != ptr::null_mut() { + if rexmpp_xml_match(child, namespace, name) > 0 { + return child; + } + unsafe { child = (*child).next }; + } + ptr::null_mut() + }, + _ => ptr::null_mut() + } +} + + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_eq (n1: *const RexmppXML, n2: *const RexmppXML) -> bool { + if n1 == n2 { + return true; + } + if n1 == ptr::null_mut() || n1 == ptr::null_mut() { + return false; + } + unsafe { + match (*n1, *n2) { + (RexmppXML { alt : RexmppXMLAlt::Text (text1), + next: _ }, + RexmppXML { alt : RexmppXMLAlt::Text (text2), + next: _ } + ) => strcmp(text1, text2) == 0, + (RexmppXML + { alt : RexmppXMLAlt::Elem + ( RexmppXMLAltElem { + qname: RexmppXMLQName { + name: name1, + namespace: namespace1 + }, + attributes: attributes1, + children: children1 + } ), + next: _}, + RexmppXML + { alt : RexmppXMLAlt::Elem + ( RexmppXMLAltElem { + qname: RexmppXMLQName { + name: name2, + namespace: namespace2 + }, + attributes: attributes2, + children: children2 + } ), + next: _} + ) => { + // Compare names + if strcmp(name1, name2) != 0 + { return false; } + // Compare namespaces + if namespace1 != namespace2 && + (namespace1 == ptr::null_mut() || + namespace2 == ptr::null_mut() || + strcmp(namespace1, namespace2) != 0) + { return false; } + // Compare attributes + let mut attr1 = attributes1; + let mut attr2 = attributes2; + while ! (attr1 == ptr::null_mut() && attr2 == ptr::null_mut()) { + if attr1 == ptr::null_mut() { + return false; + } + if attr2 == ptr::null_mut() { + return false; + } + if strcmp((*attr1).qname.name, (*attr2).qname.name) != 0 { + return false; + } + if strcmp((*attr1).value, (*attr2).value) != 0 { + return false; + } + attr1 = (*attr1).next; + attr2 = (*attr2).next; + } + // Compare children + let mut child1 = children1; + let mut child2 = children2; + while ! (child1 == ptr::null_mut() && child2 == ptr::null_mut()) + { + if child1 == ptr::null_mut() { + return false; + } + if child2 == ptr::null_mut() { + return false; + } + if ! rexmpp_xml_eq(child1, child2) { + return false; + } + child1 = (*child1).next; + child2 = (*child2).next; + } + true + } + _ => false + } + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_children (node: *const RexmppXML) + -> *mut RexmppXML { + if node == ptr::null_mut() { + return ptr::null_mut(); + } + match unsafe { (*node).alt } { + RexmppXMLAlt::Elem(elem) => elem.children, + _ => ptr::null_mut() + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_first_elem_child (node: *mut RexmppXML) + -> *mut RexmppXML { + let mut child: *mut RexmppXML = rexmpp_xml_children(node); + while child != ptr::null_mut() { + if matches!(unsafe { (*child).alt }, RexmppXMLAlt::Elem(_)) { + return child; + } + unsafe { child = (*child).next }; + } + return ptr::null_mut(); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_next_elem_sibling (node: *mut RexmppXML) + -> *mut RexmppXML { + if node == ptr::null_mut() { + return ptr::null_mut(); + } + let mut sibling: *mut RexmppXML = unsafe { (*node).next }; + while sibling != ptr::null_mut() { + if matches!(unsafe { (*sibling).alt }, RexmppXMLAlt::Elem(_)) { + return sibling; + } + unsafe { sibling = (*sibling).next }; + } + return ptr::null_mut(); +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_text (node: *mut RexmppXML) + -> *mut c_char { + if node == ptr::null_mut() { + return ptr::null_mut(); + } + match unsafe { (*node).alt } { + RexmppXMLAlt::Text(text) => text, + _ => ptr::null_mut() + } +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_text_child (node: *mut RexmppXML) + -> *mut c_char { + rexmpp_xml_text(rexmpp_xml_children(node)) +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_reverse_list (mut node: *mut RexmppXML) + -> *mut RexmppXML { + let mut next; + let mut prev = ptr::null_mut(); + while node != ptr::null_mut() { + unsafe { + next = (*node).next; + (*node).next = prev; + prev = node; + node = next; + } + } + return prev; +} + +#[no_mangle] +pub extern "C" +fn rexmpp_xml_reverse_children (node: *mut RexmppXML) + -> *mut RexmppXML { + unsafe { + if node == ptr::null_mut() { + return node; + } + match (*node).alt { + RexmppXMLAlt::Elem(ref mut elem) => { + (*elem).children = rexmpp_xml_reverse_list((*elem).children); + let mut cur = node; + while cur != ptr::null_mut() { + match (*cur).alt { + RexmppXMLAlt::Elem(ref mut cur_elem) => { + (*cur_elem).children = + rexmpp_xml_reverse_children((*cur_elem).children); + }, + _ => () + } + cur = (*cur).next; + } + }, + _ => () + } + } + return node; +} diff --git a/src/rexmpp_xml_parser.c b/src/rexmpp_xml_parser.c new file mode 100644 index 0000000..3f485d7 --- /dev/null +++ b/src/rexmpp_xml_parser.c @@ -0,0 +1,323 @@ +/** + @file rexmpp_xml_parser.c + @brief XML parsing for rexmpp + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#include <string.h> + +#include "rexmpp.h" +#include "rexmpp_xml.h" +#include "rexmpp_xml_parser.h" +#include "config.h" + +#if defined(USE_LIBXML2) + +void rexmpp_xml_sax_characters (rexmpp_xml_parser_ctx_t ctx, + const char *ch, + int len) +{ + ctx->handlers->text(ctx->user_data, ch, len); +} + +void rexmpp_xml_sax_elem_start (rexmpp_xml_parser_ctx_t ctx, + 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)prefix; + (void)nb_namespaces; + (void)namespaces; + (void)nb_defaulted; + rexmpp_xml_attr_t *attrs = NULL; + int i; + for (i = nb_attributes - 1; i >= 0; i--) { + size_t attr_len = attributes[i * 5 + 4] - attributes[i * 5 + 3]; + char *attr_val = malloc(attr_len + 1); + if (attr_val != NULL) { + attr_val[attr_len] = '\0'; + strncpy(attr_val, attributes[i * 5 + 3], attr_len); + rexmpp_xml_attr_t *attr = + rexmpp_xml_attr_new(attributes[i * 5], NULL, attr_val); + free(attr_val); + attr->next = attrs; + attrs = attr; + } + } + + ctx->handlers->elem_start(ctx->user_data, localname, URI, attrs); +} + +void rexmpp_xml_sax_elem_end (rexmpp_xml_parser_ctx_t ctx, + const char *localname, + const char *prefix, + const char *URI) +{ + (void)localname; + (void)prefix; + (void)URI; + ctx->handlers->elem_end(ctx->user_data); +} + +xmlSAXHandler rexmpp_xml_parser_sax = { + .initialized = XML_SAX2_MAGIC, + .characters = (charactersSAXFunc)rexmpp_xml_sax_characters, + .startElementNs = (startElementNsSAX2Func)rexmpp_xml_sax_elem_start, + .endElementNs = (endElementNsSAX2Func)rexmpp_xml_sax_elem_end, +}; + + +/* rexmpp_xml_t *rexmpp_xml_from_libxml2 (xmlNodePtr from) { */ +/* if (from == NULL) { */ +/* return NULL; */ +/* } */ + +/* rexmpp_xml_t *to = NULL; */ +/* if (from->type == XML_ELEMENT_NODE) { */ +/* to = malloc(sizeof(rexmpp_xml_t)); */ + +/* /\* Type *\/ */ +/* to->type = REXMPP_XML_ELEMENT; */ + +/* /\* Name and namespace *\/ */ +/* to->alt.elem.qname.name = strdup(from->name); */ +/* if (from->nsDef != NULL && from->nsDef->href != NULL) { */ +/* to->alt.elem.qname.namespace = strdup(from->nsDef->href); */ +/* } else { */ +/* to->alt.elem.qname.namespace = NULL; */ +/* } */ + +/* /\* Attributes *\/ */ +/* to->alt.elem.attributes = NULL; */ +/* struct _xmlAttr *from_attr; */ +/* rexmpp_xml_attr_t **to_next_attr = &(to->alt.elem.attributes); */ +/* for (from_attr = from->properties; */ +/* from_attr != NULL; */ +/* from_attr = from_attr->next) */ +/* { */ +/* rexmpp_xml_attr_t *to_attr = */ +/* malloc(sizeof(rexmpp_xml_attr_t)); */ +/* to_attr->qname.name = strdup(from_attr->name); */ +/* to_attr->qname.namespace = NULL; */ +/* if (from_attr->ns != NULL && from_attr->ns->href != NULL) { */ +/* to_attr->qname.namespace = strdup(from_attr->ns->href); */ +/* to_attr->value = */ +/* xmlGetNsProp(from, to_attr->qname.name, to_attr->qname.namespace); */ +/* } else { */ +/* to_attr->value = xmlGetProp(from, to_attr->qname.name); */ +/* } */ +/* to_attr->next = NULL; */ + +/* *to_next_attr = to_attr; */ +/* to_next_attr = &(to_attr->next); */ +/* } */ + +/* /\* Children *\/ */ +/* to->alt.elem.children = NULL; */ +/* xmlNodePtr from_child; */ +/* rexmpp_xml_t **to_next_child = &(to->alt.elem.children); */ +/* for (from_child = from->children; */ +/* from_child != NULL; */ +/* from_child = from_child->next) */ +/* { */ +/* rexmpp_xml_t *next_child = rexmpp_xml_from_libxml2(from_child); */ +/* if (next_child != NULL) { */ +/* *to_next_child = next_child; */ +/* to_next_child = &(next_child->next); */ +/* } */ +/* } */ + +/* /\* Next *\/ */ +/* to->next = NULL; */ + +/* } else if (from->type == XML_TEXT_NODE) { */ +/* to = malloc(sizeof(rexmpp_xml_t)); */ +/* to->type = REXMPP_XML_TEXT; */ +/* to->alt.text = xmlNodeGetContent(from); */ +/* to->next = NULL; */ +/* } */ +/* return to; */ +/* } */ + +/* rexmpp_xml_t *rexmpp_xml_from_libxml2_list (xmlNodePtr from) { */ +/* if (from == NULL) { */ +/* return NULL; */ +/* } */ +/* rexmpp_xml_t *to = rexmpp_xml_from_libxml2(from); */ +/* if (from->next != NULL) { */ +/* to->next = rexmpp_xml_from_libxml2_list(from->next); */ +/* } */ +/* return to; */ +/* } */ + +/* xmlNodePtr rexmpp_xml_to_libxml2 (rexmpp_xml_t *from) { */ +/* if (from == NULL) { */ +/* return NULL; */ +/* } */ + +/* if (from->type == REXMPP_XML_TEXT) { */ +/* xmlNodePtr to = xmlNewText(from->alt.text); */ +/* to->next = rexmpp_xml_to_libxml2(from->next); */ +/* return to; */ +/* } */ + +/* /\* Name and namespace *\/ */ +/* xmlNodePtr to = xmlNewNode(NULL, from->alt.elem.qname.name); */ +/* if (from->alt.elem.qname.namespace != NULL) { */ +/* xmlNewNs(to, from->alt.elem.qname.namespace, NULL); */ +/* } */ + +/* /\* Attributes *\/ */ +/* rexmpp_xml_attr_t *attr = from->alt.elem.attributes; */ +/* while (attr != NULL) { */ +/* /\* TODO: Would be nice to take namespaces into account, though */ +/* they are currently not used for attributes. *\/ */ +/* xmlNewProp(to, attr->qname.name, attr->value); */ +/* attr = attr->next; */ +/* } */ + +/* /\* Children *\/ */ +/* rexmpp_xml_t *child = from->alt.elem.children; */ +/* while (child != NULL) { */ +/* xmlAddChild(to, rexmpp_xml_to_libxml2(child)); */ +/* child = child->next; */ +/* } */ +/* return to; */ +/* } */ + +/* xmlNodePtr rexmpp_xml_to_libxml2_list (rexmpp_xml_t *from) { */ +/* xmlNodePtr to = rexmpp_xml_to_libxml2(from); */ +/* if (from->next != NULL) { */ +/* xmlAddNextSibling(to, rexmpp_xml_to_libxml2_list(from->next)); */ +/* } */ +/* return to; */ +/* } */ + +#elif defined(USE_EXPAT) + +void XMLCALL +rexmpp_xml_sax_elem_start (rexmpp_xml_parser_ctx_t ctx, + const char *el, + const char **attributes) +{ + char *buf = strdup(el); + char *name = NULL, *namespace = buf; + size_t i; + for (i = 0; i < strlen(namespace); i++) { + if (namespace[i] == '\xff') { + name = namespace + i + 1; + namespace[i] = '\0'; + } + } + if (name == NULL) { + name = namespace; + namespace = NULL; + } + rexmpp_xml_attr_t *attrs = NULL; + for (i = 0; attributes[i] != NULL; i += 2) { + rexmpp_xml_attr_t *attr = + rexmpp_xml_attr_new(attributes[i], NULL, attributes[i + 1]); + attr->next = attrs; + attrs = attr; + } + + ctx->handlers->elem_start(ctx->user_data, name, namespace, attrs); + free(buf); +} + +void XMLCALL +rexmpp_xml_sax_elem_end(rexmpp_xml_parser_ctx_t ctx, + const XML_Char *name) +{ + (void)name; + ctx->handlers->elem_end(ctx->user_data); +} + +void XMLCALL +rexmpp_xml_sax_characters (rexmpp_xml_parser_ctx_t ctx, + const XML_Char *ch, + int len) +{ + ctx->handlers->text(ctx->user_data, ch, len); +} + +#endif + + + +rexmpp_xml_parser_ctx_t +rexmpp_xml_parser_new (rexmpp_xml_parser_handlers_t handlers, + void *data) +{ + rexmpp_xml_parser_ctx_t ctx = malloc(sizeof(struct rexmpp_xml_parser_ctx)); + if (ctx == NULL) { + return NULL; + } +#if defined(USE_LIBXML2) + xmlParserCtxtPtr p = + xmlCreatePushParserCtxt(&rexmpp_xml_parser_sax, ctx, "", 0, NULL); +#elif defined(USE_EXPAT) + XML_Parser p = XML_ParserCreateNS("utf-8", '\xff'); + XML_SetUserData(p, ctx); + XML_SetStartElementHandler(p, (XML_StartElementHandler) + rexmpp_xml_sax_elem_start); + XML_SetEndElementHandler(p, (XML_EndElementHandler) + rexmpp_xml_sax_elem_end); + XML_SetCharacterDataHandler(p, (XML_CharacterDataHandler) + rexmpp_xml_sax_characters); +#endif + if (p == NULL) { + free(ctx); + return NULL; + } + + ctx->xml_parser = p; + ctx->handlers = handlers; + ctx->user_data = data; + return ctx; +} + +void rexmpp_xml_parser_free (rexmpp_xml_parser_ctx_t ctx) { +#if defined(USE_LIBXML2) + xmlFreeParserCtxt(ctx->xml_parser); +#elif defined(USE_EXPAT) + XML_ParserFree(ctx->xml_parser); +#endif + free(ctx); +} + +rexmpp_xml_parser_ctx_t rexmpp_xml_parser_reset (rexmpp_xml_parser_ctx_t ctx) { +#if defined(USE_LIBXML2) + xmlCtxtResetPush(ctx->xml_parser, "", 0, "", "utf-8"); +#elif defined(USE_EXPAT) + XML_ParserReset(ctx->xml_parser, "utf-8"); + XML_SetUserData(ctx->xml_parser, ctx); + XML_SetStartElementHandler(ctx->xml_parser, (XML_StartElementHandler) + rexmpp_xml_sax_elem_start); + XML_SetEndElementHandler(ctx->xml_parser, (XML_EndElementHandler) + rexmpp_xml_sax_elem_end); + XML_SetCharacterDataHandler(ctx->xml_parser, (XML_CharacterDataHandler) + rexmpp_xml_sax_characters); +#endif + return ctx; +} + +void +rexmpp_xml_parser_feed (rexmpp_xml_parser_ctx_t ctx, + const char *chunk, + size_t len, + int final) +{ +#if defined(USE_LIBXML2) + xmlParseChunk(ctx->xml_parser, chunk, len, final); +#elif defined(USE_EXPAT) + XML_Parse(ctx->xml_parser, chunk, len, final); +#endif +} diff --git a/src/rexmpp_xml_parser.h b/src/rexmpp_xml_parser.h new file mode 100644 index 0000000..66627ab --- /dev/null +++ b/src/rexmpp_xml_parser.h @@ -0,0 +1,106 @@ +/** + @file rexmpp_xml_parser.h + @brief XML parsing for rexmpp + @author defanor <defanor@uberspace.net> + @date 2023 + @copyright MIT license. +*/ + +#ifndef REXMPP_XML_PARSER_H +#define REXMPP_XML_PARSER_H + + +#if defined(USE_LIBXML2) + #include <libxml/tree.h> +#elif defined(USE_EXPAT) + #include <expat.h> +#endif + +#include "config.h" + +typedef void (*rexmpp_xml_parser_element_start) (void *data, + const char *name, + const char *namespace, + rexmpp_xml_attr_t *attributes); +typedef void (*rexmpp_xml_parser_element_end) (void *data); +typedef void (*rexmpp_xml_parser_characters) (void *data, + const char *ch, + size_t len); + +struct rexmpp_xml_parser_handlers { + rexmpp_xml_parser_element_start elem_start; + rexmpp_xml_parser_element_end elem_end; + rexmpp_xml_parser_characters text; +}; + + +typedef struct rexmpp_xml_parser_ctx* rexmpp_xml_parser_ctx_t; +typedef struct rexmpp_xml_parser_handlers* rexmpp_xml_parser_handlers_t; + +struct rexmpp_xml_parser_ctx { +#if defined(USE_LIBXML2) + xmlParserCtxtPtr xml_parser; +#elif defined(USE_EXPAT) + XML_Parser xml_parser; +#else + void *xml_parser; +#endif + rexmpp_xml_parser_handlers_t handlers; + void *user_data; +}; + +/** + @brief Allocates a new XML parser context + @param[in] handlers SAX-like parser event handlers + @param[in] data User-provided data to pass to the handlers + @returns A parser context pointer, or NULL on failure. +*/ +rexmpp_xml_parser_ctx_t +rexmpp_xml_parser_new (rexmpp_xml_parser_handlers_t handlers, + void *data); + +/** + @brief Frees an XML parser context + @param[in] ctx An XML parser context +*/ +void rexmpp_xml_parser_free (rexmpp_xml_parser_ctx_t ctx); + +/** + @brief Feeds data to parse into an XML parser + @param[in] ctx An XML parser context + @param[in] chunk A chunk of data to parse + @param[in] len Length of the data chunk +*/ +void +rexmpp_xml_parser_feed (rexmpp_xml_parser_ctx_t ctx, + const char *chunk, + size_t len, + int final); + +/** + @brief Resets a parser context + @param[in] ctx An XML parser context + @returns A new pointer, since it may change during a reset +*/ +rexmpp_xml_parser_ctx_t rexmpp_xml_parser_reset (rexmpp_xml_parser_ctx_t ctx); + + +/* #if defined(USE_LIBXML2) */ +/* /\** */ +/* @brief Creates a single ::rexmpp_xml_t XML node out of libxml2's */ +/* xmlNode, without siblings. */ +/* *\/ */ +/* rexmpp_xml_t *rexmpp_xml_from_libxml2 (xmlNodePtr from); */ + +/* /\** */ +/* @brief Creates a ::rexmpp_xml_t XML node out of libxml2's xmlNode, */ +/* with siblings. */ +/* *\/ */ +/* rexmpp_xml_t *rexmpp_xml_from_libxml2_list (xmlNodePtr from); */ + +/* xmlNodePtr rexmpp_xml_to_libxml2 (rexmpp_xml_t *from); */ + +/* xmlNodePtr rexmpp_xml_to_libxml2_list (rexmpp_xml_t *from); */ +/* #endif */ + +#endif diff --git a/src/rexmpp_xml_parser.rs b/src/rexmpp_xml_parser.rs new file mode 100644 index 0000000..037c2f2 --- /dev/null +++ b/src/rexmpp_xml_parser.rs @@ -0,0 +1,145 @@ +extern crate libc; +extern crate rxml; +use libc::{free, strndup}; +use std::ptr; +use std::os::raw::{c_char, c_void}; +use std::ffi::{CStr, CString}; +use std::slice; +use rxml::{FeedParser, Error, ResolvedEvent, XmlVersion, EventRead, CData}; +use std::io; +use super::{rexmpp_xml}; + +type RexmppXMLParserElementStart = unsafe extern "C" +fn (data: *mut c_void, + name: *const c_char, + namespace: *const c_char, + attributes: *mut rexmpp_xml::RexmppXMLAttribute) -> (); + +type RexmppXMLParserElementEnd = unsafe extern "C" +fn (data: *mut c_void) -> (); + +type RexmppXMLParserCharacters = unsafe extern "C" +fn (data: *mut c_void, + ch: *const c_char, + len: usize) -> (); + +#[repr(C)] +struct RexmppXMLParserHandlers { + elem_start: RexmppXMLParserElementStart, + elem_end: RexmppXMLParserElementEnd, + text: RexmppXMLParserCharacters +} + +#[repr(C)] +struct RexmppXMLParserCtx { + xml_parser: *mut FeedParser, + handlers: *mut RexmppXMLParserHandlers, + user_data: *mut c_void +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_parser_new (handlers: *mut RexmppXMLParserHandlers, + data: *mut c_void) + -> *mut RexmppXMLParserCtx +{ + let mut fp = FeedParser::default(); + let ctx = RexmppXMLParserCtx { + xml_parser: Box::into_raw(Box::new(fp)), + handlers: handlers, + user_data: data + }; + Box::into_raw(Box::new(ctx)) +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_parser_free (ctx: *mut RexmppXMLParserCtx) { + unsafe { free(ctx as *mut c_void) }; +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_parser_feed (ctx: *mut RexmppXMLParserCtx, + chunk: *const c_char, + len: usize, + is_final: bool) +{ + unsafe { + // todo: maybe duplicate the string, since apparently a + // mutable one is expected by the parser. + let mut buf : &[u8] = slice::from_raw_parts(chunk as *mut u8, len); + let user_data_ptr = (*ctx).user_data; + let handlers = (*ctx).handlers; + (*((*ctx).xml_parser)).parse_all(&mut buf, is_final, |ev| { + match ev { + ResolvedEvent::StartElement(_, (namespace, name), attrs) => + { + let name_str = name.to_string(); + let ns_opt_cstr : Option<CString> = match namespace { + None => None, + Some(ns_arc_name) => { + match CString::new(ns_arc_name.to_string()) { + Ok(cstr) => Some(cstr), + Err(_) => None + } + } + }; + match CString::new(name_str) { + Ok(name_cstr) => { + let name_cstr_ptr = name_cstr.as_ptr(); + let namespace_cstr_ptr = + match ns_opt_cstr { + None => ptr::null_mut(), + // "ref" is important to use here, + // otherwise the pointer will be + // wrong. + Some(ref ns_cstr) => ns_cstr.as_ptr() + }; + let mut attributes = ptr::null_mut(); + for ((_, attr_name), attr_val) in attrs.iter() { + match (CString::new(attr_name.to_string()), + CString::new(attr_val.to_string())) { + (Ok(attr_name_cstr), Ok(attr_val_cstr)) => { + let attr = + rexmpp_xml::rexmpp_xml_attr_new + (attr_name_cstr.as_ptr(), + ptr::null_mut(), + attr_val_cstr.as_ptr()); + (*attr).next = attributes; + attributes = attr; + }, + _ => () + } + } + ((*handlers).elem_start) + (user_data_ptr, + name_cstr_ptr, + namespace_cstr_ptr, + attributes); + }, + Err(_) => () + } + }, + ResolvedEvent::EndElement(_) => + ((*handlers).elem_end)(user_data_ptr), + ResolvedEvent::Text(_, cd) => + ((*handlers).text)( + user_data_ptr, + cd.as_ptr() as *const i8, + cd.len() + ), + _ => () + } + }); + } +} + +#[no_mangle] +extern "C" +fn rexmpp_xml_parser_reset (ctx_raw: *mut RexmppXMLParserCtx) + -> *mut RexmppXMLParserCtx +{ + let ctx = unsafe { Box::from_raw(ctx_raw) }; + rexmpp_xml_parser_new((*ctx).handlers, (*ctx).user_data) +} |