summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2021-10-13 22:03:49 +0300
committerdefanor <defanor@uberspace.net>2021-10-13 22:52:44 +0300
commit2e667698024e85fe1a9ae44181af9e38141176eb (patch)
tree0ed19b48b792fdf7f5ac14d569e5f7cd978adbd6
parentf2ef0cf34d32e26c45004a5f2bd57380c15c720d (diff)
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.
-rw-r--r--README14
-rw-r--r--configure.ac13
-rw-r--r--examples/basic.c5
-rw-r--r--src/Makefile.am5
-rw-r--r--src/rexmpp.c92
-rw-r--r--src/rexmpp.h92
-rw-r--r--src/rexmpp_console.c44
-rw-r--r--src/rexmpp_jingle.c1774
-rw-r--r--src/rexmpp_jingle.h93
-rw-r--r--src/rexmpp_tls.c18
-rw-r--r--src/rexmpp_tls.h4
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 <jid>\n"
"subscription deny <jid>\n"
"http-upload <file path>\n"
+ "jingle terminate <sid>\n"
+ "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"
;
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 <string.h>
@@ -18,6 +29,19 @@ The following XEPs are handled here so far:
#include <libgen.h>
#include <gcrypt.h>
+#include "config.h"
+
+#ifdef ENABLE_CALLS
+#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>
+#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 <crypto/> 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,
+ <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) {
+ 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 <glib.h>
+#include <agent.h>
+#include <gnutls/gnutls.h>
+#include <srtp2/srtp.h>
+#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 <jingle/> 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,