From 2e667698024e85fe1a9ae44181af9e38141176eb Mon Sep 17 00:00:00 2001 From: defanor Date: Wed, 13 Oct 2021 22:03:49 +0300 Subject: Implement Jingle RTP sessions with ICE-UDP and DTLS-SRTP Works with Dino and Conversations, but currently relying on external players and streamers for actual audio playback and capture. For now requiring GnuTLS and libnice for calls; OpenSSL should be supported as an alternative to the former, and the latter should be made optional, maybe with libjuice as an alternative. --- README | 14 +- configure.ac | 13 + examples/basic.c | 5 +- src/Makefile.am | 5 +- src/rexmpp.c | 92 ++- src/rexmpp.h | 92 +-- src/rexmpp_console.c | 44 +- src/rexmpp_jingle.c | 1774 ++++++++++++++++++++++++++++++++++++++++++++++---- src/rexmpp_jingle.h | 93 ++- src/rexmpp_tls.c | 18 + src/rexmpp_tls.h | 4 + 11 files changed, 1952 insertions(+), 202 deletions(-) diff --git a/README b/README index 770b3c0..5177630 100644 --- a/README +++ b/README @@ -15,7 +15,8 @@ of implementing additional XEPs on top of it, and should try to make it easy to implement a decent client application using it. Current dependencies: libxml2, libgcrypt. Optionally gsasl, libunbound -or c-ares, gnutls with gnutls-dane or openssl, icu-i18n, gpgme, curl. +or c-ares, gnutls with gnutls-dane or openssl, icu-i18n, gpgme, curl, +libnice (with glib), libsrtp2. A rough roadmap: @@ -69,15 +70,22 @@ A rough roadmap: [+] XEP-0373 v0.6: OpenPGP for XMPP [+] XEP-0402 v1.1: PEP Native Bookmarks (autojoin conferences) [+] XEP-0363 v1.0: HTTP File Upload (when built with curl) -[+] XEP-0166 v1.1: Jingle (only file transfers over IBB for now) +[+] XEP-0166 v1.1: Jingle [+] XEP-0234 v0.19: Jingle File Transfer (sending and accepting, but no requests and no ranged transfers) [+] XEP-0261 v1.0: Jingle In-Band Bytestreams Transport Method +[+] XEP-0167 v1.2: Jingle RTP Sessions (relaying RTP packets over an + encrypted channel to/from local UDP ports, for use with external + players and streamers) +[+] XEP-0176 v1.1: Jingle ICE-UDP Transport Method (uses libnice, + discovers TURN/STUN servers with XEP-0215: External Service + Discovery) +[+] XEP-0320 v1.0: Use of DTLS-SRTP in Jingle Sessions (uses gnutls + and libsrtp2) [ ] XEP-0260: Jingle SOCKS5 Bytestreams Transport Method? [ ] XEP-0391: Jingle Encrypted Transports? [ ] XEP-0184: Message Delivery Receipts? [ ] OTR/OMEMO/MLS encryption? -[ ] A/V calls? - Additional state tracking: diff --git a/configure.ac b/configure.ac index 3e4bdd0..f2052df 100644 --- a/configure.ac +++ b/configure.ac @@ -21,6 +21,19 @@ PKG_CHECK_MODULES([LIBXML], [libxml-2.0]) AM_PATH_LIBGCRYPT +# libnice (+ glib) and libsrtp for media calls, optional + +AC_ARG_ENABLE([calls], AS_HELP_STRING([--disable-calls], + [build without Jingle media call support])) + +AS_IF([test "x$enable_calls" != "xno"], + [PKG_CHECK_MODULES([NICE], [nice], + [AC_DEFINE([HAVE_NICE], [1], [libnice is available])]) + PKG_CHECK_MODULES([GLIB], [glib-2.0], + [AC_DEFINE([HAVE_GLIB], [1], [glib is available])]) + PKG_CHECK_MODULES([SRTP], [libsrtp2], + [AC_DEFINE([HAVE_SRTP], [1], [libsrtp2 is available])]) + AC_DEFINE([ENABLE_CALLS], [1], [Jingle ICE-UDP DTLS-SRTP calls are enabled])]) # GSASL, optional diff --git a/examples/basic.c b/examples/basic.c index 3406044..6e195bd 100644 --- a/examples/basic.c +++ b/examples/basic.c @@ -136,13 +136,14 @@ int main (int argc, char **argv) { } /* Could set a client certificate for SASL EXTERNAL authentication - here. */ - /* rexmpp_tls_set_x509_key_file(&s, "client.crt", "client.key"); */ + and Jingle's DTLS here. */ + rexmpp_tls_set_x509_key_file(&s, "client.crt", "client.key"); /* Could also set various other things manually. */ /* s.socks_host = "127.0.0.1"; */ /* s.socks_port = 4321; */ /* s.manual_host = "localhost"; */ + s.local_address = "192.168.1.8"; /* rexmpp_tls_set_x509_trust_file(&s, "localhost.crt"); */ /* rexmpp_openpgp_set_home_dir(&s, "pgp"); */ s.roster_cache_file = "roster.xml"; diff --git a/src/Makefile.am b/src/Makefile.am index 437acc9..9fff4c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -31,8 +31,9 @@ include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) \ $(GNUTLS_CFLAGS) $(LIBDANE_CFLAGS) $(OPENSSL_CFLAGS) \ $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(CARES_CFLAGS) $(GPGME_CFLAGS) \ - $(ICU_I18N_CFLAGS) $(LIBGCRYPT_CFLAGS) $(CURL_CFLAGS) + $(ICU_I18N_CFLAGS) $(LIBGCRYPT_CFLAGS) $(CURL_CFLAGS) \ + $(NICE_CFLAGS) $(GLIB_CFLAGS) $(SRTP_CFLAGS) librexmpp_la_LIBADD = $(LIBXML_LIBS) \ $(GNUTLS_LIBS) $(LIBDANE_LIBS) $(OPENSSL_LIBS) \ $(GSASL_LIBS) $(UNBOUND_LIBS) $(CARES_LIBS) $(GPGME_LIBS) $(ICU_I18N_LIBS) \ - $(LIBGCRYPT_LIBS) $(CURL_LIBS) + $(LIBGCRYPT_LIBS) $(CURL_LIBS) $(NICE_LIBS) $(GLIB_LIBS) $(SRTP_LIBS) diff --git a/src/rexmpp.c b/src/rexmpp.c index cf81c70..f041765 100644 --- a/src/rexmpp.c +++ b/src/rexmpp.c @@ -490,6 +490,20 @@ xmlNodePtr rexmpp_disco_info (rexmpp_t *s) { cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ibb:1"); prev->next = cur; prev = cur; +#ifdef ENABLE_CALLS + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:dtls:0"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:transports:ice-udp:1"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:1"); + prev->next = cur; + prev = cur; + cur = rexmpp_xml_feature("urn:xmpp:jingle:apps:rtp:audio"); + prev->next = cur; + prev = cur; +#endif } cur = rexmpp_xml_feature("urn:xmpp:ping"); prev->next = cur; @@ -539,6 +553,8 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, 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->send_buffer = NULL; s->send_queue = NULL; s->server_srv = NULL; @@ -559,7 +575,6 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->stream_id = NULL; s->active_iq = NULL; s->iq_cache = NULL; - s->jingle = NULL; s->reconnect_number = 0; s->next_reconnect_time.tv_sec = 0; s->next_reconnect_time.tv_usec = 0; @@ -581,6 +596,42 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, s->last_network_activity = 0; 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); + if (jid == NULL) { rexmpp_log(s, LOG_CRIT, "No initial JID is provided."); return REXMPP_E_JID; @@ -630,6 +681,13 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, return REXMPP_E_SASL; } + if (rexmpp_jingle_init(s)) { + rexmpp_sasl_ctx_deinit(s); + rexmpp_tls_deinit(s); + rexmpp_dns_ctx_deinit(s); + xmlFreeParserCtxt(s->xml_parser); + } + #ifdef HAVE_GPGME gpgme_check_version(NULL); err = gpgme_new(&(s->pgp_ctx)); @@ -639,6 +697,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s, rexmpp_sasl_ctx_deinit(s); rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); + rexmpp_jingle_stop(s); xmlFreeParserCtxt(s->xml_parser); return REXMPP_E_PGP; } @@ -729,6 +788,7 @@ void rexmpp_iq_finish (rexmpp_t *s, /* Frees the things that persist through reconnects. */ void rexmpp_done (rexmpp_t *s) { + rexmpp_jingle_stop(s); rexmpp_cleanup(s); #ifdef HAVE_CURL curl_multi_cleanup(s->curl_multi); @@ -741,6 +801,10 @@ void rexmpp_done (rexmpp_t *s) { rexmpp_tls_deinit(s); rexmpp_dns_ctx_deinit(s); xmlFreeParserCtxt(s->xml_parser); + if (s->jingle_rtp_description != NULL) { + xmlFreeNode(s->jingle_rtp_description); + s->jingle_rtp_description = NULL; + } if (s->stream_id != NULL) { free(s->stream_id); s->stream_id = NULL; @@ -779,7 +843,6 @@ void rexmpp_done (rexmpp_t *s) { xmlFreeNodeList(s->iq_cache); s->iq_cache = NULL; } - rexmpp_jingle_stop(s); } void rexmpp_schedule_reconnect (rexmpp_t *s) { @@ -861,7 +924,7 @@ int rexmpp_xml_match (xmlNodePtr node, } } if (namespace != NULL) { - if (node->ns == NULL) { + if (node->nsDef == NULL || node->nsDef->href == NULL) { if (strcmp(namespace, "jabber:client") != 0) { return 0; } @@ -2513,11 +2576,8 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { /* Resolving SRV records. This continues in rexmpp_srv_tls_cb, rexmpp_srv_cb. */ - if (s->resolver_state != REXMPP_RESOLVER_NONE && - s->resolver_state != REXMPP_RESOLVER_READY) { - if (rexmpp_dns_process(s, read_fds, write_fds)) { - return REXMPP_E_DNS; - } + if (rexmpp_dns_process(s, read_fds, write_fds)) { + return REXMPP_E_DNS; } /* Initiating a connection after SRV resolution. */ @@ -2555,6 +2615,9 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } } + /* Jingle activity. */ + rexmpp_jingle_run(s, read_fds, write_fds); + /* The things we do while connected. */ /* Sending queued data. */ @@ -2653,11 +2716,13 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { } int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { - int conn_fd, tls_fd, max_fd = 0; + int conn_fd, tls_fd, jingle_fd, max_fd = 0; - if (s->resolver_state != REXMPP_RESOLVER_NONE && - s->resolver_state != REXMPP_RESOLVER_READY) { - max_fd = rexmpp_dns_fds(s, read_fds, write_fds); + max_fd = rexmpp_dns_fds(s, read_fds, write_fds); + + jingle_fd = rexmpp_jingle_fds(s, read_fds, write_fds); + if (jingle_fd > max_fd) { + max_fd = jingle_fd; } #ifdef HAVE_CURL @@ -2718,6 +2783,9 @@ struct timeval *rexmpp_timeout (rexmpp_t *s, } else if (s->tcp_state == REXMPP_TCP_CONNECTING) { ret = rexmpp_tcp_conn_timeout(s, &s->server_connection, max_tv, tv); } + + ret = rexmpp_jingle_timeout(s, ret, tv); + struct timeval now; gettimeofday(&now, NULL); if (s->reconnect_number > 0 && diff --git a/src/rexmpp.h b/src/rexmpp.h index 22722ce..c6d4428 100644 --- a/src/rexmpp.h +++ b/src/rexmpp.h @@ -70,48 +70,6 @@ enum rexmpp_err { REXMPP_E_OTHER }; -typedef enum rexmpp_err rexmpp_err_t; - -#include "rexmpp_tcp.h" -#include "rexmpp_socks.h" -#include "rexmpp_dns.h" -#include "rexmpp_tls.h" -#include "rexmpp_jid.h" -#include "rexmpp_jingle.h" -#include "rexmpp_sasl.h" - -/** - @brief An info/query callback function type. - @param[in,out] s A ::rexmpp structure. - @param[in] request A request that was made. - @param[in] response A response we have received. @c NULL if we are - giving up on this IQ. - - A callback must not free the request or the response, but merely - inspect those and react. -*/ -typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s, - void *cb_data, - xmlNodePtr request, - xmlNodePtr response, - int success); - -typedef struct rexmpp_iq rexmpp_iq_t; - -/** @brief A pending info/query request. */ -struct rexmpp_iq -{ - /** @brief The sent request. */ - xmlNodePtr request; - /** @brief A callback to call on reply. */ - rexmpp_iq_callback_t cb; - /** @brief User-supplied data, to pass to a callback function. */ - void *cb_data; - /** @brief Next pending IQ. */ - rexmpp_iq_t *next; -}; - - /** @brief DNS resolver state */ enum resolver_st { REXMPP_RESOLVER_NONE, @@ -227,6 +185,47 @@ enum tls_pol { REXMPP_TLS_AVOID }; +typedef enum rexmpp_err rexmpp_err_t; + +#include "rexmpp_tcp.h" +#include "rexmpp_socks.h" +#include "rexmpp_dns.h" +#include "rexmpp_tls.h" +#include "rexmpp_jid.h" +#include "rexmpp_jingle.h" +#include "rexmpp_sasl.h" + +/** + @brief An info/query callback function type. + @param[in,out] s A ::rexmpp structure. + @param[in] request A request that was made. + @param[in] response A response we have received. @c NULL if we are + giving up on this IQ. + + A callback must not free the request or the response, but merely + inspect those and react. +*/ +typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s, + void *cb_data, + xmlNodePtr request, + xmlNodePtr response, + int success); + +typedef struct rexmpp_iq rexmpp_iq_t; + +/** @brief A pending info/query request. */ +struct rexmpp_iq +{ + /** @brief The sent request. */ + xmlNodePtr request; + /** @brief A callback to call on reply. */ + rexmpp_iq_callback_t cb; + /** @brief User-supplied data, to pass to a callback function. */ + void *cb_data; + /** @brief Next pending IQ. */ + rexmpp_iq_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); @@ -277,6 +276,8 @@ struct rexmpp 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; /* Resource limits. */ uint32_t stanza_queue_size; @@ -303,6 +304,9 @@ struct rexmpp /* Other dynamic data. */ xmlNodePtr disco_info; + /* Includes Jingle RTP session candidates; rexmpp prioritizes the + ones listed earlier on incoming calls. */ + xmlNodePtr jingle_rtp_description; /* IQs we're waiting for responses to. */ rexmpp_iq_t *active_iq; @@ -310,8 +314,8 @@ struct rexmpp /* Cached IQ requests and responses. */ xmlNodePtr iq_cache; - /* Active Jingle sessions. */ - rexmpp_jingle_session_t *jingle; + /* Jingle context. */ + rexmpp_jingle_ctx_t jingle; /* Connection and stream management. */ unsigned int reconnect_number; diff --git a/src/rexmpp_console.c b/src/rexmpp_console.c index 397e603..f2d748f 100644 --- a/src/rexmpp_console.c +++ b/src/rexmpp_console.c @@ -301,8 +301,12 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { "subscription approve \n" "subscription deny \n" "http-upload \n" + "jingle terminate \n" + "jingle decline \n" "jingle accept-file \n" "jingle send-file \n" + "jingle accept-call \n" + "jingle call \n" ; if (! strcmp(word, "help")) { @@ -566,14 +570,48 @@ void rexmpp_console_feed (rexmpp_t *s, char *str, ssize_t str_len) { if (word == NULL) { return; } - if (! strcmp(word, "accept-file")) { + if (! strcmp(word, "terminate")) { + char *sid = strtok_r(NULL, " ", &words_save_ptr); + if (sid != NULL) { + rexmpp_jingle_session_terminate(s, sid, + rexmpp_xml_new_node("success", + "urn:xmpp:jingle:1"), + NULL); + } + } else if (! strcmp(word, "decline")) { + char *sid = strtok_r(NULL, " ", &words_save_ptr); + if (sid != NULL) { + rexmpp_jingle_session_terminate(s, sid, + rexmpp_xml_new_node("decline", + "urn:xmpp:jingle:1"), + NULL); + } + } else if (! strcmp(word, "accept-file")) { char *sid = strtok_r(NULL, " ", &words_save_ptr); char *fpath = strtok_r(NULL, " ", &words_save_ptr); - rexmpp_jingle_accept_file_by_id(s, sid, fpath); + if (sid != NULL && fpath != NULL) { + rexmpp_jingle_accept_file_by_id(s, sid, fpath); + } } else if (! strcmp(word, "send-file")) { char *jid = strtok_r(NULL, " ", &words_save_ptr); char *fpath = strtok_r(NULL, " ", &words_save_ptr); - rexmpp_jingle_send_file(s, jid, fpath); + if (jid != NULL && fpath != NULL) { + rexmpp_jingle_send_file(s, jid, fpath); + } + } 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)); + } + } 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)); + } } } } diff --git a/src/rexmpp_jingle.c b/src/rexmpp_jingle.c index 453ada8..3a72a95 100644 --- a/src/rexmpp_jingle.c +++ b/src/rexmpp_jingle.c @@ -8,8 +8,19 @@ The following XEPs are handled here so far: - XEP-0166: Jingle + +File transfer over IBB: + - XEP-0234: Jingle File Transfer - XEP-0261: Jingle In-Band Bytestreams Transport Method + +A/V calls over ICE-UDP + DTLS-SRTP: + +- XEP-0167: Jingle RTP Sessions +- XEP-0176: Jingle ICE-UDP Transport Method +- XEP-0320: Use of DTLS-SRTP in Jingle Sessions +- XEP-0215: External Service Discovery + */ #include @@ -18,6 +29,19 @@ The following XEPs are handled here so far: #include #include +#include "config.h" + +#ifdef ENABLE_CALLS +#include +#include +#include +#include +#include +#include +#include +#include +#endif + #include "rexmpp.h" #include "rexmpp_jingle.h" #include "rexmpp_base64.h" @@ -28,7 +52,7 @@ rexmpp_jingle_session_by_id (rexmpp_t *s, const char *sid) { if (sid == NULL) { return NULL; } - rexmpp_jingle_session_t *cur = s->jingle; + rexmpp_jingle_session_t *cur = s->jingle.sessions; while (cur != NULL) { if (strcmp(cur->sid, sid) == 0) { return cur; @@ -39,23 +63,6 @@ rexmpp_jingle_session_by_id (rexmpp_t *s, const char *sid) { return NULL; } -rexmpp_jingle_session_t * -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; - while (cur != NULL) { - if (strcmp(cur->ibb_sid, ibb_sid) == 0) { - return cur; - } - cur = cur->next; - } - rexmpp_log(s, LOG_WARNING, - "No Jingle session with ibb_sid %s found", ibb_sid); - return NULL; -} - void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) { if (session->jid != NULL) { free(session->jid); @@ -63,12 +70,63 @@ void rexmpp_jingle_session_destroy (rexmpp_jingle_session_t *session) { if (session->sid != NULL) { free(session->sid); } - if (session->negotiation != NULL) { - xmlFreeNodeList(session->negotiation); + if (session->initiate != NULL) { + xmlFreeNodeList(session->initiate); } - if (session->f != NULL) { - fclose(session->f); + if (session->accept != NULL) { + xmlFreeNodeList(session->accept); + } + if (session->ibb_fh != NULL) { + fclose(session->ibb_fh); + } + #ifdef ENABLE_CALLS + if (session->type == REXMPP_JINGLE_SESSION_MEDIA) { + int i; + for (i = 0; i < 2; i++) { + rexmpp_jingle_component_t *comp = &session->component[i]; + if (comp->dtls_state == REXMPP_TLS_ACTIVE || + comp->dtls_state == REXMPP_TLS_CLOSING || + comp->dtls_state == REXMPP_TLS_CLOSED) { + /* 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->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); + comp->dtls_state = REXMPP_TLS_INACTIVE; + } + if (comp->udp_socket != -1) { + close(comp->udp_socket); + comp->udp_socket = -1; + } + } + if (session->ice_agent != NULL) { + g_object_unref(session->ice_agent); + session->ice_agent = NULL; + } + if (session->stun_host != NULL) { + free(session->stun_host); + session->stun_host = NULL; + } + if (session->turn_host != NULL) { + free(session->turn_host); + session->turn_host = NULL; + } + if (session->turn_username != NULL) { + free(session->turn_username); + session->turn_username = NULL; + } + if (session->turn_password != NULL) { + free(session->turn_password); + session->turn_password = NULL; + } } + #endif free(session); } @@ -77,26 +135,29 @@ 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 **next_ptr = &(s->jingle), *cur = s->jingle; + rexmpp_jingle_session_t *cur = s->jingle.sessions, *prev = NULL; while (cur != NULL) { if (sess == cur) { - *next_ptr = cur->next; + if (prev == NULL) { + s->jingle.sessions = cur->next; + } else { + prev->next = cur->next; + } rexmpp_jingle_session_destroy(sess); + return; } - next_ptr = &(cur->next); + prev = cur; cur = cur->next; } } -void rexmpp_jingle_stop (rexmpp_t *s) { - while (s->jingle != NULL) { - rexmpp_jingle_session_delete(s, s->jingle); - } +void rexmpp_jingle_session_delete_by_id (rexmpp_t *s, const char *sid) { + rexmpp_jingle_session_delete(s, rexmpp_jingle_session_by_id(s, 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; + rexmpp_jingle_session_t *cur = s->jingle.sessions; while (cur != NULL) { sessions_num++; cur = cur->next; @@ -107,13 +168,100 @@ 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; - s->jingle = sess; + sess->next = s->jingle.sessions; + s->jingle.sessions = sess; return 1; } -void rexmpp_jingle_session_delete_by_id (rexmpp_t *s, const char *sid) { - rexmpp_jingle_session_delete(s, rexmpp_jingle_session_by_id(s, sid)); +int rexmpp_jingle_ice_agent_init (rexmpp_jingle_session_t *sess); + +rexmpp_jingle_session_t * +rexmpp_jingle_session_create (rexmpp_t *s, + char *jid, + char *sid, + enum rexmpp_jingle_session_type type, + int initiator) +{ + 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->ibb_fh = NULL; + sess->ibb_sid = NULL; + sess->ibb_seq = 0; +#ifdef ENABLE_CALLS + 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->ice_agent = NULL; + sess->rtcp_mux = s->jingle_prefer_rtcp_mux; + + 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 + if (! rexmpp_jingle_session_add(s, sess)) { + rexmpp_jingle_session_destroy(sess); + sess = NULL; + } + } else { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a Jingle session"); + } + return sess; +} + +rexmpp_jingle_session_t * +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; + while (cur != NULL) { + if (cur->type == REXMPP_JINGLE_SESSION_FILE && + strcmp(cur->ibb_sid, ibb_sid) == 0) { + return cur; + } + cur = cur->next; + } + rexmpp_log(s, LOG_WARNING, + "No Jingle session with ibb_sid %s found", ibb_sid); + return NULL; +} + +int rexmpp_jingle_init (rexmpp_t *s) { + s->jingle.sessions = NULL; +#ifdef ENABLE_CALLS + g_networking_init(); + srtp_init(); + s->jingle.gloop = g_main_loop_new(NULL, FALSE); +#endif + return 0; +} + +void rexmpp_jingle_stop (rexmpp_t *s) { + 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; + srtp_shutdown(); +#endif } @@ -138,13 +286,13 @@ rexmpp_jingle_accept_file (rexmpp_t *s, rexmpp_jingle_session_t *session, const char *path) { - session->f = fopen(path, "wb"); - if (session->f == NULL) { + session->ibb_fh = fopen(path, "wb"); + if (session->ibb_fh == NULL) { rexmpp_log(s, LOG_ERR, "Failed to open %s for writing: %s", path, strerror(errno)); return REXMPP_E_OTHER; } - xmlNodePtr jingle = session->negotiation; + xmlNodePtr jingle = session->initiate; xmlNodePtr content = rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); xmlNodePtr new_jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); @@ -152,8 +300,7 @@ rexmpp_jingle_accept_file (rexmpp_t *s, xmlNewProp(new_jingle, "responder", s->assigned_jid.full); xmlNewProp(new_jingle, "sid", session->sid); xmlAddChild(new_jingle, xmlCopyNode(content, 1)); - xmlFreeNode(session->negotiation); - session->negotiation = xmlCopyNode(new_jingle, 1); + session->accept = xmlCopyNode(new_jingle, 1); return rexmpp_iq_new(s, "set", session->jid, new_jingle, rexmpp_jingle_accept_file_cb, strdup(session->sid)); } @@ -163,8 +310,11 @@ rexmpp_jingle_accept_file_by_id (rexmpp_t *s, const char *sid, const char *path) { - return - rexmpp_jingle_accept_file(s, rexmpp_jingle_session_by_id(s, sid), path); + rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid); + if (session == NULL) { + return REXMPP_E_OTHER; + } + return rexmpp_jingle_accept_file(s, session, path); } void rexmpp_jingle_session_terminate_cb (rexmpp_t *s, @@ -180,7 +330,6 @@ void rexmpp_jingle_session_terminate_cb (rexmpp_t *s, rexmpp_log(s, LOG_ERR, "Failed to terminate session %s, removing anyway", sid); } - rexmpp_jingle_session_delete_by_id(s, sid); free(sid); } @@ -205,20 +354,11 @@ rexmpp_jingle_session_terminate (rexmpp_t *s, } xmlAddChild(reason, reason_node); xmlAddChild(jingle, reason); - return rexmpp_iq_new(s, "set", session->jid, jingle, - rexmpp_jingle_session_terminate_cb, strdup(sid)); -} - -rexmpp_err_t -rexmpp_jingle_accept_file_by_sid (rexmpp_t *s, - const char *sid, - const char *path) -{ - rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_id(s, sid); - if (session == NULL) { - return REXMPP_E_OTHER; - } - return rexmpp_jingle_accept_file(s, session, path); + rexmpp_err_t ret = rexmpp_iq_new(s, "set", session->jid, jingle, + rexmpp_jingle_session_terminate_cb, + strdup(sid)); + rexmpp_jingle_session_delete_by_id(s, sid); + return ret; } void rexmpp_jingle_send_file_cb (rexmpp_t *s, @@ -242,11 +382,37 @@ rexmpp_jingle_send_file (rexmpp_t *s, const char *jid, char *path) { + /* Open the file and calculate its hash before allocating the other + things, so we can easily return on failure. */ FILE *fh = fopen(path, "rb"); if (fh == NULL) { rexmpp_log(s, LOG_ERR, "Failed to open %s for reading", path); return REXMPP_E_OTHER; } + + 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)); + 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)); + fclose(fh); + return REXMPP_E_OTHER; + } + size_t len = fread(buf, 1, 4096, fh); + while (len > 0) { + gcry_md_write(hd, 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); @@ -276,19 +442,6 @@ rexmpp_jingle_send_file (rexmpp_t *s, xmlNodeAddContent(file_name, basename(path)); xmlAddChild(file, file_name); - char buf[4096]; - - gcry_md_hd_t hd; - /* todo: check for hashing errors */ - gcry_md_open(&hd, GCRY_MD_SHA256, 0); - gcry_md_enable(hd, GCRY_MD_SHA3_256); - size_t len = fread(buf, 1, 4096, fh); - while (len > 0) { - gcry_md_write(hd, buf, len); - len = fread(buf, 1, 4096, fh); - } - gcry_md_final(hd); - char *hash_base64 = NULL; size_t hash_base64_len = 0; rexmpp_base64_to(gcry_md_read(hd, GCRY_MD_SHA256), @@ -323,14 +476,12 @@ rexmpp_jingle_send_file (rexmpp_t *s, xmlNodeAddContent(file_size, buf); xmlAddChild(file, file_size); - rexmpp_jingle_session_t *sess = malloc(sizeof(rexmpp_jingle_session_t)); - sess->jid = strdup(jid); - sess->sid = sid; - sess->ibb_sid = ibb_sid; - sess->ibb_seq = 0; - sess->negotiation = xmlCopyNode(jingle, 1); - sess->f = fh; - if (rexmpp_jingle_session_add(s, sess)) { + 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->ibb_sid = ibb_sid; + sess->ibb_fh = fh; return rexmpp_iq_new(s, "set", sess->jid, jingle, rexmpp_jingle_send_file_cb, strdup(sess->sid)); } else { @@ -338,11 +489,12 @@ rexmpp_jingle_send_file (rexmpp_t *s, } } -void rexmpp_jingle_close_cb (rexmpp_t *s, - void *ptr, - xmlNodePtr request, - xmlNodePtr response, - int success) + +void rexmpp_jingle_ibb_close_cb (rexmpp_t *s, + void *ptr, + xmlNodePtr request, + xmlNodePtr response, + int success) { (void)request; (void)response; @@ -355,11 +507,11 @@ void rexmpp_jingle_close_cb (rexmpp_t *s, free(sid); } -void rexmpp_jingle_send_cb (rexmpp_t *s, - void *ptr, - xmlNodePtr request, - xmlNodePtr response, - int success) +void rexmpp_jingle_ibb_send_cb (rexmpp_t *s, + void *ptr, + xmlNodePtr request, + xmlNodePtr response, + int success) { (void)request; (void)response; @@ -376,15 +528,15 @@ void rexmpp_jingle_send_cb (rexmpp_t *s, free(sid); return; } - if (feof(session->f)) { + if (feof(session->ibb_fh)) { xmlNodePtr close = rexmpp_xml_new_node("close", "http://jabber.org/protocol/ibb"); xmlNewProp(close, "sid", session->ibb_sid); rexmpp_iq_new(s, "set", session->jid, close, - rexmpp_jingle_close_cb, sid); + rexmpp_jingle_ibb_close_cb, sid); return; } else { char buf[4096]; - size_t len = fread(buf, 1, 4096, session->f); + 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); @@ -397,19 +549,952 @@ void rexmpp_jingle_send_cb (rexmpp_t *s, xmlNewProp(data, "seq", buf); session->ibb_seq++; rexmpp_iq_new(s, "set", session->jid, data, - rexmpp_jingle_send_cb, sid); + rexmpp_jingle_ibb_send_cb, sid); return; } 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", "urn:xmpp:jingle:1"), - NULL); + "File reading error"); } } free(sid); } +#ifdef ENABLE_CALLS +void rexmpp_jingle_call_cb (rexmpp_t *s, + void *ptr, + xmlNodePtr request, + xmlNodePtr response, + int success) +{ + (void)request; + (void)response; + char *sid = ptr; + if (! success) { + rexmpp_log(s, LOG_ERR, "Failed to initiate a call for sid %s", sid); + rexmpp_jingle_session_delete_by_id(s, sid); + } + free(sid); +} + +void +rexmpp_jingle_ice_udp_add_remote (rexmpp_jingle_session_t *sess, + xmlNodePtr transport) +{ + if (sess->ice_agent == NULL) { + /* Must be an incoming call; just add candidates to + session-initiate's transport. */ + xmlNodePtr 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); + while (rexmpp_xml_match(candidate, "urn:xmpp:jingle:transports:ice-udp:1", + "candidate")) { + xmlAddChild(old_transport, xmlCopyNode(candidate, 1)); + candidate = candidate->next; + } + return; + } + char *ufrag = xmlGetProp(transport, "ufrag"); + char *password = xmlGetProp(transport, "pwd"); + nice_agent_set_remote_credentials(sess->ice_agent, sess->ice_stream_id, + ufrag, password); + free(ufrag); + free(password); + + int component_id; + + for (component_id = 1; component_id <= (sess->rtcp_mux ? 1 : 2); component_id++) { + GSList *remote_candidates = + 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; + + char *foundation = xmlGetProp(candidate, "foundation"); + strncpy(c->foundation, foundation, NICE_CANDIDATE_MAX_FOUNDATION - 1); + c->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0; + free(foundation); + + c->transport = NICE_CANDIDATE_TRANSPORT_UDP; + + char *priority = xmlGetProp(candidate, "priority"); + c->priority = atoi(priority); + free(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); + + char *port = xmlGetProp(candidate, "port"); + nice_address_set_port(&c->addr, atoi(port)); + free(port); + + remote_candidates = g_slist_prepend(remote_candidates, c); + } + free(component); + candidate = candidate->next; + } + if (remote_candidates != NULL) { + 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); + } + } +} + +/* 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_find_child + (rexmpp_xml_find_child + (rexmpp_xml_find_child + (in_initiate ? sess->initiate : sess->accept, + "urn:xmpp:jingle:1", "content"), + "urn:xmpp:jingle:transports:ice-udp:1", "transport"), + "urn:xmpp:jingle:apps:dtls:0", "fingerprint"); + if (fingerprint == NULL) { + rexmpp_log(sess->s, LOG_ERR, "No fingerprint in the 'session-%s' Jingle element", + in_initiate ? "initiate" : "accept"); + return 0; + } + char *fingerprint_setup = xmlGetProp(fingerprint, "setup"); + if (fingerprint_setup == NULL) { + rexmpp_log(sess->s, LOG_ERR, "No 'setup' attribute for a fingerprint element"); + return 0; + } + int active = 0; + if (sess->initiator) { + if (in_initiate) { + active = (strcmp(fingerprint_setup, "active") == 0); + } else { + active = (strcmp(fingerprint_setup, "active") != 0); + } + } else { + if (in_initiate) { + active = (strcmp(fingerprint_setup, "active") != 0); + } else { + 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, + int success) +{ + (void)request; + (void)response; + char *sid = ptr; + if (! success) { + rexmpp_log(s, LOG_ERR, + "Failed to send additional candidate for Jingle session %s", + sid); + } + free(ptr); +} + +void +rexmpp_jingle_candidate_gathering_done_cb (NiceAgent *agent, + guint stream_id, + gpointer data) +{ + 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; + } + + char fp[32], fp_str[97]; + 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]); + } + gnutls_free(cert_list); + + xmlNodePtr jingle = rexmpp_xml_new_node("jingle", "urn:xmpp:jingle:1"); + xmlNewProp(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; + if (sess->initiator) { + xmlNewProp(jingle, "action", "session-initiate"); + xmlNewProp(jingle, "initiator", sess->s->assigned_jid.full); + xmlNewProp(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); + } else { + xmlNodePtr init_jingle = sess->initiate; + xmlNodePtr init_content = + rexmpp_xml_find_child(init_jingle, "urn:xmpp:jingle:1", "content"); + char *init_content_name = xmlGetProp(init_content, "name"); + if (init_content_name != NULL) { + xmlNewProp(content, "name", init_content_name); + free(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); + + description = xmlCopyNode(sess->s->jingle_rtp_description, 2); + /* 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) { + if (rexmpp_xml_match(pl_type, "urn:xmpp:jingle:apps:rtp:1", "payload-type")) { + char *pl_id = xmlGetProp(pl_type, "id"); + if (pl_id != NULL) { + int pl_id_num = atoi(pl_id); + xmlNodePtr proposed_pl_type = + xmlFirstElementChild + (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"); + 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"); + if (pl_name != NULL) { + char *proposed_pl_name = xmlGetProp(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; + } + free(pl_id); + } else { + rexmpp_log(sess->s, LOG_ERR, + "No 'id' specified for a pyaload-type element."); + } + } + pl_type = pl_type->next; + } + if (selected_pl != NULL) { + xmlAddChild(description, xmlCopyNode(selected_pl, 1)); + } else { + rexmpp_log(sess->s, LOG_ERR, "No suitable payload type found"); + /* todo: fail if it's NULL, though it shouldn't happen, since + PCMU and PCMA are mandatory */ + } + } + + xmlAddChild(jingle, content); + xmlAddChild(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); + } + + xmlNodePtr transport = + rexmpp_xml_new_node("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); + g_free(ufrag); + g_free(password); + xmlAddChild(content, transport); + int component_id; + xmlNodePtr 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"); + 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); + free(cid); + nice_address_to_string(&c->addr, buf); + xmlNewProp(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"); + snprintf(buf, 11, "%u", c->priority); + xmlNewProp(candidate, "priority", buf); + char *nice_type[] = {"host", "srflx", "prflx", "relay"}; + if (c->type < 4) { + xmlNewProp(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. */ + if (cand_num < 10) { + xmlAddChild(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); + jingle_ti->next = postponed_candidates; + postponed_candidates = jingle_ti; + } + cand_cur = cand_cur->next; + cand_num++; + } + if (candidates != NULL) { + g_slist_free_full(candidates, (GDestroyNotify)&nice_candidate_free); + } + } + + xmlNodePtr fingerprint = + rexmpp_xml_new_node("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + xmlNewProp(fingerprint, "hash", "sha-256"); + if (sess->initiator) { + xmlNewProp(fingerprint, "setup", "actpass"); + } else if (rexmpp_jingle_dtls_is_active(sess, 1)) { + xmlNewProp(fingerprint, "setup", "active"); + } else { + xmlNewProp(fingerprint, "setup", "passive"); + } + + xmlNodeAddContent(fingerprint, fp_str); + xmlAddChild(transport, fingerprint); + + if (sess->initiator) { + sess->initiate = xmlCopyNode(jingle, 1); + } else { + sess->accept = xmlCopyNode(jingle, 1); + } + + rexmpp_iq_new(sess->s, "set", sess->jid, jingle, + rexmpp_jingle_call_cb, strdup(sess->sid)); + + /* Now send transport-info messages with candidates that didn't fit + initially. */ + while (postponed_candidates != NULL) { + xmlNodePtr 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)); + postponed_candidates = pc_next; + } +} + +ssize_t +rexmpp_jingle_dtls_push_func (gnutls_transport_ptr_t p, const void *data, size_t size) +{ + rexmpp_jingle_component_t *comp = p; + rexmpp_jingle_session_t *sess = comp->session; + return nice_agent_send(sess->ice_agent, sess->ice_stream_id, + 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; +} + +ssize_t +rexmpp_jingle_dtls_pull_func (gnutls_transport_ptr_t p, + void *data, + size_t size) +{ + 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); +} + +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; + + struct sockaddr_in cli_addr; + socklen_t cli_addr_size; + int ret; + 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); + + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms % 1000) * 1000; + + ret = select(fd + 1, &rfds, NULL, NULL, &tv); + if (ret <= 0) { + 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; + } + + 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); +} + +void +rexmpp_jingle_component_state_changed_cb (NiceAgent *agent, + guint stream_id, + guint component_id, + guint state, + gpointer data) +{ + rexmpp_jingle_session_t *sess = data; + (void)agent; + if (component_id < 1 || component_id > 2) { + rexmpp_log(sess->s, LOG_CRIT, "Unexpected ICE component_id: %d", + component_id); + return; + } + if (state == NICE_COMPONENT_STATE_READY) { + rexmpp_log(sess->s, LOG_INFO, + "ICE connection established for Jingle session %s, " + "ICE stream %d, component %d", + sess->sid, stream_id, component_id); + if (sess->component[component_id - 1].dtls_state != REXMPP_TLS_INACTIVE) { + rexmpp_log(sess->s, LOG_WARNING, + "The connection for Jingle session %s and component %d" + " was established previously", + sess->sid, component_id); + 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); + sess->component[component_id - 1].dtls_state = REXMPP_TLS_HANDSHAKE; + /* todo: use the profile/crypto-suite from element */ + gnutls_srtp_set_profile(*tls_session, GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80); + gnutls_handshake(*tls_session); + + } 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", + sess->sid, stream_id, component_id); + /* todo: maybe destroy the session if it failed for all the + components */ + } +} + +void +rexmpp_jingle_ice_recv_cb (NiceAgent *agent, guint stream_id, guint component_id, + guint len, gchar *buf, 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; + 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) { + int err; + srtp_ctx_t *srtp_in; + if (comp->dtls_state == REXMPP_TLS_ACTIVE) { + srtp_in = comp->srtp_in; + } else if (comp->session->component[0].dtls_state == REXMPP_TLS_ACTIVE) { + /* Allow to reuse the first component's DTLS handshake/SRTP + session. */ + srtp_in = comp->session->component[0].srtp_in; + } else { + rexmpp_log(comp->s, LOG_WARNING, + "Received an SRTP packet while DTLS is inactive"); + return; + } + uint16_t port_out = comp->udp_port_out; + if (component_id == 1) { + err = srtp_unprotect(srtp_in, buf, &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; + } + } else { + err = srtp_unprotect_rtcp(srtp_in, buf, &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)); + } + } 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"); + } + } +} + +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), + NICE_COMPATIBILITY_RFC5245); + if (sess->s->local_address != NULL) { + NiceAddress *address = nice_address_new(); + nice_address_set_from_string(address, sess->s->local_address); + nice_agent_add_local_address(sess->ice_agent, address); + nice_address_free(address); + } + g_object_set(sess->ice_agent, "controlling-mode", sess->initiator, NULL); + g_signal_connect(sess->ice_agent, "candidate-gathering-done", + G_CALLBACK(rexmpp_jingle_candidate_gathering_done_cb), sess); + g_signal_connect(sess->ice_agent, "component-state-changed", + G_CALLBACK(rexmpp_jingle_component_state_changed_cb), sess); + + sess->ice_stream_id = nice_agent_add_stream(sess->ice_agent, sess->rtcp_mux ? 1 : 2); + if (sess->ice_stream_id == 0) { + rexmpp_log(sess->s, LOG_ERR, "Failed to add an ICE agent stream"); + g_object_unref(sess->ice_agent); + sess->ice_agent = NULL; + return 0; + } + + 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), + rexmpp_jingle_ice_recv_cb, + &sess->component[i]); + } + + 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) { + /* Only using the first address. */ + struct in_addr addr; + memcpy(&addr, + result->data[0], + result->len[0]); + char *ip = inet_ntoa(addr); + rexmpp_log(s, LOG_DEBUG, "Resolved TURN server's hostname to %s (%ssecure)", + ip, result->secure ? "" : "in"); + /* Adding it just for the first component for now. */ + nice_agent_set_relay_info(sess->ice_agent, sess->ice_stream_id, 1, + ip, sess->turn_port, + sess->turn_username, sess->turn_password, + NICE_RELAY_TYPE_TURN_UDP); + } else { + rexmpp_log(s, LOG_WARNING, "Failed to resolve TURN server's address"); + } + nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id); + rexmpp_dns_result_free(result); +} + +void rexmpp_jingle_stun_dns_cb (rexmpp_t *s, void *ptr, rexmpp_dns_result_t *result) { + rexmpp_jingle_session_t *sess = ptr; + if (result != NULL && result->data != NULL) { + /* Only using the first address. */ + struct in_addr addr; + memcpy(&addr, + result->data[0], + result->len[0]); + char *ip = inet_ntoa(addr); + rexmpp_log(s, LOG_DEBUG, "Resolved STUN server's hostname to %s (%ssecure)", + ip, result->secure ? "" : "in"); + g_object_set(sess->ice_agent, "stun-server", ip, NULL); + g_object_set(sess->ice_agent, "stun-server-port", sess->stun_port, NULL); + } else { + rexmpp_log(s, LOG_WARNING, "Failed to resolve STUN server's address"); + } + + /* Proceed to TURN resolution if there's a TURN host, or just start + connecting. */ + if (sess->turn_host != NULL) { + rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb); + } else { + nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id); + } + rexmpp_dns_result_free(result); +} + +void rexmpp_jingle_turn_cb (rexmpp_t *s, + void *sess_ptr, + xmlNodePtr req, + xmlNodePtr response, + int success) +{ + (void)req; + rexmpp_jingle_session_t *sess = sess_ptr; + if (success) { + /* use credentials */ + xmlNodePtr services = xmlFirstElementChild(response); + if (rexmpp_xml_match(services, "urn:xmpp:extdisco:2", "services")) { + xmlNodePtr service = xmlFirstElementChild(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"); + + if (sess->stun_host == NULL && + type != NULL && transport != NULL && host != NULL && port != NULL && + strcmp(type, "stun") == 0 && strcmp(transport, "udp") == 0) { + sess->stun_host = strdup(host); + sess->stun_port = atoi(port); + rexmpp_log(s, LOG_DEBUG, "Setting STUN server to %s:%s", host, port); + } + + if (sess->turn_host == NULL && + type != NULL && transport != NULL && host != NULL && port != NULL && + username != NULL && password != NULL && + strcmp(type, "turn") == 0 && strcmp(transport, "udp") == 0) { + sess->turn_host = strdup(host); + sess->turn_port = atoi(port); + sess->turn_username = strdup(username); + 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; + } + if (sess->stun_host != NULL) { + /* Resolve, then resolve STUN host, then connect. */ + rexmpp_dns_resolve(s, sess->turn_host, 1, 1, sess, rexmpp_jingle_stun_dns_cb); + return; + } else if (sess->stun_host != NULL) { + /* Resolve, then connect. */ + /* todo: handle IPv6 too, but that's awkward enough for now to + deal with resolution before calling the library. And + hopefully IPv6 will make all this NAT traversal business + unnecessary anyway. */ + rexmpp_dns_resolve(s, sess->stun_host, 1, 1, sess, rexmpp_jingle_turn_dns_cb); + return; + } else { + rexmpp_log(s, LOG_DEBUG, "No STUN or TURN servers found"); + } + } + } else { + rexmpp_log(s, LOG_DEBUG, + "Failed to request TURN credentials, " + "trying to connect without STUN/TURN"); + } + nice_agent_gather_candidates(sess->ice_agent, sess->ice_stream_id); +} + +void rexmpp_jingle_discover_turn_cb (rexmpp_t *s, + void *sess_ptr, + xmlNodePtr req, + xmlNodePtr response, + int success) +{ + (void)req; + char *response_from = xmlGetProp(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); + } 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) { + rexmpp_disco_find_feature(s, s->initial_jid.domain, "urn:xmpp:extdisco:2", + rexmpp_jingle_discover_turn_cb, sess, 0, 1); +} + +rexmpp_err_t +rexmpp_jingle_call (rexmpp_t *s, + const char *jid, + uint16_t rtp_port_in, + uint16_t rtp_port_out) +{ + rexmpp_jingle_session_t *sess = + rexmpp_jingle_session_create(s, strdup(jid), rexmpp_gen_id(s), + 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; +} + +rexmpp_err_t +rexmpp_jingle_call_accept (rexmpp_t *s, + const char *sid, + uint16_t rtp_port_in, + uint16_t rtp_port_out) +{ + 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_find_child(sess->initiate, + "urn:xmpp:jingle:1", + "content"); + xmlNodePtr ice_udp_transport = + rexmpp_xml_find_child(content, + "urn:xmpp:jingle:transports:ice-udp:1", + "transport"); + if (ice_udp_transport == NULL) { + 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"), + "No ICE-UDP transport defined"); + return REXMPP_E_OTHER; + } + rexmpp_jingle_ice_udp_add_remote(sess, ice_udp_transport); + rexmpp_jingle_discover_turn(s, sess); + return REXMPP_SUCCESS; +} +#else + +rexmpp_err_t +rexmpp_jingle_call (rexmpp_t *s, + const char *jid, + uint16_t rtp_port_in, + uint16_t rtp_port_out) +{ + (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) +{ + (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 + int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { int handled = 0; if (! s->enable_jingle) { @@ -424,48 +1509,80 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { 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_find_child(jingle, "urn:xmpp:jingle:1", "content"); + xmlNodePtr 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")); } else { rexmpp_iq_reply(s, elem, "result", NULL); - xmlNodePtr description = + xmlNodePtr file_description = rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5", "description"); - xmlNodePtr transport = + xmlNodePtr ibb_transport = rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ibb:1", "transport"); - if (description == NULL) { - rexmpp_jingle_session_terminate(s, sid, - rexmpp_xml_new_node("unsupported-applications", - "urn:xmpp:jingle:1"), - NULL); - } else if (transport == NULL) { + xmlNodePtr ice_udp_transport = + rexmpp_xml_find_child(content, "urn:xmpp:jingle:transports:ice-udp:1", + "transport"); + xmlNodePtr 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"); + if (ibb_sid != NULL) { + rexmpp_log(s, LOG_DEBUG, + "Jingle session-initiate from %s, sid %s, ibb sid %s", + from_jid, sid, ibb_sid); + rexmpp_jingle_session_t *sess = + 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; + } else { + rexmpp_jingle_session_terminate(s, sid, + rexmpp_xml_new_node("failed-transport", + "urn:xmpp:jingle:1"), + NULL); + } + } else { + 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", + "urn:xmpp:jingle:1"), + NULL); + } +#ifdef ENABLE_CALLS + } else if (ice_udp_transport != NULL && rtp_description != NULL) { + rexmpp_log(s, LOG_DEBUG, "Jingle session-initiate from %s, sid %s", + from_jid, sid); + 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 + } else if (file_description == NULL && + rtp_description == NULL) { + rexmpp_jingle_session_terminate + (s, sid, + rexmpp_xml_new_node("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); } else { - char *ibb_sid = xmlGetProp(transport, "sid"); - if (ibb_sid != NULL) { - rexmpp_jingle_session_t *sess = malloc(sizeof(rexmpp_jingle_session_t)); - sess->jid = strdup(from_jid); - sess->sid = strdup(sid); - sess->ibb_sid = ibb_sid; - sess->ibb_seq = 0; - sess->negotiation = xmlCopyNode(jingle, 1); - sess->f = NULL; - rexmpp_log(s, LOG_DEBUG, "Jingle session-initiate from %s, sid %s", - sess->jid, sid); - rexmpp_jingle_session_add(s, sess); - } else { - 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", - "urn:xmpp:jingle:1"), - NULL); - } + /* todo: some other error */ } } } else if (strcmp(action, "session-terminate") == 0) { @@ -476,12 +1593,48 @@ 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) { - 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_iq_new(s, "set", session->jid, open, - rexmpp_jingle_send_cb, strdup(sid)); + session->accept = xmlCopyNode(jingle, 1); + xmlNodePtr content = + rexmpp_xml_find_child(jingle, "urn:xmpp:jingle:1", "content"); + xmlNodePtr file_description = + rexmpp_xml_find_child(content, "urn:xmpp:jingle:apps:file-transfer:5", + "description"); + xmlNodePtr 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_iq_new(s, "set", session->jid, open, + rexmpp_jingle_ibb_send_cb, strdup(sid)); + } else { +#ifdef ENABLE_CALLS + xmlNodePtr 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 + } + } + } 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_find_child(jingle, "urn:xmpp:jingle:1", "content"); + xmlNodePtr 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 } } else { rexmpp_log(s, LOG_WARNING, "Unknown Jingle action: %s", action); @@ -503,13 +1656,15 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { } /* XEP-0261: Jingle In-Band Bytestreams Transport Method */ - xmlNodePtr ibb_open = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "open"); + xmlNodePtr 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_find_child(elem, "http://jabber.org/protocol/ibb", "close"); + xmlNodePtr 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); @@ -525,13 +1680,14 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { free(sid); } } - xmlNodePtr ibb_data = rexmpp_xml_find_child(elem, "http://jabber.org/protocol/ibb", "data"); + xmlNodePtr 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"); if (sid != NULL) { rexmpp_jingle_session_t *session = rexmpp_jingle_session_by_ibb_sid(s, sid); - if (session != NULL && session->f != NULL) { + if (session != NULL && session->ibb_fh != NULL) { char *data = NULL, *data_base64 = xmlNodeGetContent(ibb_data); if (data_base64 != NULL) { size_t data_len = 0; @@ -541,9 +1697,10 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { if (base64_err != 0) { rexmpp_log(s, LOG_ERR, "Base-64 decoding failure"); } else { - size_t written = fwrite(data, 1, data_len, session->f); + size_t written = fwrite(data, 1, data_len, session->ibb_fh); if (written != data_len) { - rexmpp_log(s, LOG_ERR, "Wrote %d bytes, expected %d", written, data_len); + rexmpp_log(s, LOG_ERR, "Wrote %d bytes, expected %d", + written, data_len); /* todo: maybe introduce buffering, or make it an error */ } } @@ -556,3 +1713,356 @@ int rexmpp_jingle_iq (rexmpp_t *s, xmlNodePtr elem) { } return handled; } + +int rexmpp_jingle_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { + int nfds = -1; +#ifdef ENABLE_CALLS + gint poll_timeout; + GPollFD poll_fds[10]; + 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, + &poll_timeout, + (GPollFD *)&poll_fds, + 10); + g_main_context_release(gctx); + int i; + for (i = 0; i < poll_fds_n; i++) { + if (poll_fds[i].events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) { + FD_SET(poll_fds[i].fd, read_fds); + } + if (poll_fds[i].events & (G_IO_OUT | G_IO_ERR)) { + FD_SET(poll_fds[i].fd, write_fds); + } + if (poll_fds[i].fd > nfds) { + nfds = poll_fds[i].fd; + } + } + + rexmpp_jingle_session_t *sess; + 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); + 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; + } + } + } + } + } + } else { + rexmpp_log(s, LOG_ERR, + "Failed to acquire GMainContext in rexmpp_jingle_fds"); + } +#else + (void)s; + (void)read_fds; + (void)write_fds; +#endif + return (nfds + 1); +} + +struct timeval * rexmpp_jingle_timeout (rexmpp_t *s, + struct timeval *max_tv, + struct timeval *tv) { +#ifdef ENABLE_CALLS + gint poll_timeout; + GPollFD poll_fds[10]; + GMainContext* gctx = g_main_loop_get_context(s->jingle.gloop); + if (g_main_context_acquire(gctx)) { + g_main_context_query(gctx, + G_PRIORITY_HIGH, + &poll_timeout, + (GPollFD *)&poll_fds, + 10); + g_main_context_release(gctx); + + rexmpp_jingle_session_t *sess; + 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); + if (tms > 0 && (poll_timeout < 0 || tms < poll_timeout)) { + poll_timeout = tms; + } + } + } + } + + if (poll_timeout >= 0) { + int sec = poll_timeout / 1000; + int usec = (poll_timeout % 1000) * 1000; + if (max_tv == NULL || + (max_tv->tv_sec > sec || + (max_tv->tv_sec == sec && max_tv->tv_usec > usec))) { + tv->tv_sec = sec; + tv->tv_usec = usec; + max_tv = tv; + } + } + } else { + rexmpp_log(s, LOG_ERR, + "Failed to acquire GMainContext in rexmpp_jingle_timeout"); + } +#else + (void)s; + (void)tv; +#endif + return max_tv; +} + +rexmpp_err_t +rexmpp_jingle_run (rexmpp_t *s, + fd_set *read_fds, + fd_set *write_fds) +{ + (void)write_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) { + 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) { + 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_log(comp->s, LOG_ERR, + "Unexpected peer certificate list size: %d", + cert_list_size); + rexmpp_jingle_session_terminate + (s, sess->sid, + rexmpp_xml_new_node("security-error", "urn:xmpp:jingle:1"), + "Unexpected certificate list size; expected exactly 1."); + } 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 */ + rexmpp_log(comp->s, LOG_ERR, + "No fingerprint in the peer's Jingle element"); + rexmpp_jingle_session_terminate + (s, sess->sid, + rexmpp_xml_new_node("connectivity-error", "urn:xmpp:jingle:1"), + "No fingerprint element"); + } 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, + . */ + 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) { + rexmpp_log(comp->s, LOG_ERR, + "Unknown hash algorithm in the peer's fingerprint"); + 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; + } 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); + } + 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"); + } + } + } + } + } + } + } 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)); + 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"), + "DTLS connection error"); + break; + } + } + } + + /* Handle outbound packets */ + srtp_ctx_t *srtp_out; + 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; + } + + 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); + } + if (err) { + rexmpp_log(s, LOG_ERR, "SRT(C)P protect error %d\n", err); + } else { + nice_agent_send(sess->ice_agent, sess->ice_stream_id, + sess->rtcp_mux ? 1 : comp->component_id, + 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 + (void)s; + (void)read_fds; +#endif + return REXMPP_SUCCESS; +} diff --git a/src/rexmpp_jingle.h b/src/rexmpp_jingle.h index c8a2822..cd23efb 100644 --- a/src/rexmpp_jingle.h +++ b/src/rexmpp_jingle.h @@ -11,6 +11,16 @@ #ifndef REXMPP_JINGLE_H #define REXMPP_JINGLE_H +#include "config.h" + +#ifdef ENABLE_CALLS +#include +#include +#include +#include +#define DTLS_SRTP_BUF_SIZE 0x4000 +#endif + #include "rexmpp.h" /** @brief Processes incoming Jingle IQs. */ @@ -38,18 +48,93 @@ rexmpp_jingle_session_terminate (rexmpp_t *s, xmlNodePtr reason_node, const char *reason_text); +typedef struct rexmpp_jingle_component rexmpp_jingle_component_t; typedef struct rexmpp_jingle_session rexmpp_jingle_session_t; +typedef struct rexmpp_jingle_ctx rexmpp_jingle_ctx_t; + +enum rexmpp_jingle_session_type { + REXMPP_JINGLE_SESSION_FILE, + REXMPP_JINGLE_SESSION_MEDIA +}; + +#ifdef ENABLE_CALLS +/* A structure used for callbacks, to pass rexmpp_t, + rexmpp_jingle_session_t, and the component ID. */ +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]; + 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; +}; +#endif struct rexmpp_jingle_session { char *jid; char *sid; + xmlNodePtr initiate; + xmlNodePtr 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 + through those. */ + rexmpp_t *s; + int initiator; + enum rexmpp_jingle_session_type type; + + /* IBB file transfers */ + FILE *ibb_fh; char *ibb_sid; uint16_t ibb_seq; - /* The most recent elmment in negotiation. */ - xmlNodePtr negotiation; - FILE *f; - rexmpp_jingle_session_t *next; + + /* ICE-UDP + DTLS-SRTP A/V calls */ +#ifdef ENABLE_CALLS + char *stun_host; + uint16_t stun_port; + char *turn_host; + uint16_t turn_port; + char *turn_username; + char *turn_password; + /* two component structures for callbacks: for SRTP and SRTCP */ + rexmpp_jingle_component_t component[2]; + int rtcp_mux; + NiceAgent *ice_agent; + int ice_stream_id; +#endif +}; + +struct rexmpp_jingle_ctx { +#ifdef ENABLE_CALLS + GMainLoop* gloop; + gnutls_certificate_credentials_t dtls_cred; +#endif + rexmpp_jingle_session_t *sessions; }; +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); +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); +rexmpp_err_t +rexmpp_jingle_call_accept (rexmpp_t *s, + const char *sid, + uint16_t rtp_port_in, + uint16_t rtp_port_out); + #endif diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c index 4881647..7919556 100644 --- a/src/rexmpp_tls.c +++ b/src/rexmpp_tls.c @@ -60,6 +60,15 @@ int rexmpp_tls_init (rexmpp_t *s) { gnutls_strerror(err)); return 1; } +#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; #elif defined(USE_OPENSSL) SSL_library_init(); @@ -110,6 +119,9 @@ void rexmpp_tls_deinit (rexmpp_t *s) { free(s->tls.tls_session_data); s->tls.tls_session_data = NULL; } +#ifdef ENABLE_CALLS + gnutls_certificate_free_credentials(s->jingle.dtls_cred); +#endif #elif defined(USE_OPENSSL) if (s->tls.openssl_ctx != NULL) { SSL_CTX_free(s->tls.openssl_ctx); @@ -396,6 +408,12 @@ rexmpp_tls_set_x509_key_file (rexmpp_t *s, 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 if (ret == 0) { return REXMPP_TLS_SUCCESS; } else { diff --git a/src/rexmpp_tls.h b/src/rexmpp_tls.h index 21e61f3..24ba042 100644 --- a/src/rexmpp_tls.h +++ b/src/rexmpp_tls.h @@ -74,6 +74,10 @@ rexmpp_tls_err_t rexmpp_tls_recv(rexmpp_t *s, void *data, size_t data_size, ssiz 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. +*/ rexmpp_tls_err_t rexmpp_tls_set_x509_key_file (rexmpp_t *s, const char *cert_file, -- cgit v1.2.3