summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2021-09-19 22:05:38 +0300
committerdefanor <defanor@uberspace.net>2021-09-19 22:05:38 +0300
commitc84f9e76d8e93c37b974c0fc64a6afdf432595cc (patch)
tree0829e751c2d19d7752a8c785ac8c021bba1852ea
parent26745d3a4e21ecf46492445b9c0251d80890bf62 (diff)
Introduce OpenSSL and no-TLS options, in addition to GnuTLS
Also an option to require TLS is added. There's no DANE TLSA checks with OpenSSL yet, TLS session resumptions and ALPN aren't used with it; just basic connections with certificate verification are added. And now SASL EXTERNAL authentication isn't quite usable.
-rw-r--r--README9
-rw-r--r--configure.ac41
-rw-r--r--src/Makefile.am14
-rw-r--r--src/rexmpp.c437
-rw-r--r--src/rexmpp.h13
-rw-r--r--src/rexmpp_tls.c387
-rw-r--r--src/rexmpp_tls.h74
7 files changed, 659 insertions, 316 deletions
diff --git a/README b/README
index afe9589..02b2d26 100644
--- a/README
+++ b/README
@@ -14,8 +14,8 @@ rely on any particular UI, should be flexible and not stay in the way
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: libunbound, libxml2, gnutls, gnutls-dane, gsasl,
-gpgme, libicu, nettle.
+Current dependencies: libunbound, libxml2, gnutls with gnutls-dane or
+openssl, gsasl, gpgme, libicu, nettle.
A rough roadmap:
@@ -40,7 +40,8 @@ A rough roadmap:
[+] XEP-0368 v1.1: SRV records for XMPP over TLS.
[+] SOCKS5 (RFC 1928) support. Implemented, though no authentication.
[+] XEP-0199 v2.0: XMPP Ping.
-[.] Certificate verification using DANE (experimental).
+[.] Certificate verification using DANE (experimental, only when built
+ with GnuTLS).
- Library refinement:
@@ -48,7 +49,7 @@ A rough roadmap:
[.] Doxygen documentation.
[.] Texinfo manual.
[.] Proper JID handling (RFC 7622).
-[ ] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
+[.] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
optional usage of alternative ones. Though maybe shouldn't
abstract out XML functions and structures: could reuse existing
libxml2 bindings that way.
diff --git a/configure.ac b/configure.ac
index f88deb1..f75696b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,7 +6,7 @@ AC_INIT([rexmpp], [0.0.0], [defanor@uberspace.net])
AM_INIT_AUTOMAKE([-Werror -Wall])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_SRCDIR([src/rexmpp.c])
-AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_HEADERS([src/config.h])
AC_CONFIG_FILES([Makefile src/Makefile rexmpp.pc Doxyfile])
# Checks for programs.
@@ -17,34 +17,41 @@ LT_INIT
# Checks for libraries.
PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
-AC_SUBST(LIBXML_CFLAGS)
-AC_SUBST(LIBXML_LIBS)
-PKG_CHECK_MODULES([GNUTLS], [gnutls])
-AC_SUBST(GNUTLS_CFLAGS)
-AC_SUBST(GNUTLS_LIBS)
+AC_ARG_ENABLE([tls], AS_HELP_STRING([--disable-tls], [build without TLS support]))
+AC_ARG_WITH([openssl],
+ AS_HELP_STRING([--with-openssl], [Use OpenSSL]))
+AC_ARG_WITH([gnutls],
+ AS_HELP_STRING([--with-gnutls], [Use GnuTLS]))
+
+AS_IF([test "x$with_gnutls" == "xyes"],
+ [PKG_CHECK_MODULES([GNUTLS], [gnutls],
+ [PKG_CHECK_MODULES([LIBDANE], [gnutls-dane],
+ [AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS])])])],
+
+ [test "x$with_openssl" == "xyes"],
+ [PKG_CHECK_MODULES([OPENSSL], [openssl],
+ [AC_DEFINE([USE_OPENSSL], [1], [Use OpenSSL])])],
+
+ # Default to GnuTLS for now (check for OpenSSL if not found
+ # later).
+ [test "x$enable_tls" != "xno"],
+ [PKG_CHECK_MODULES([GNUTLS], [gnutls],
+ [PKG_CHECK_MODULES([LIBDANE], [gnutls-dane],
+ [AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS])],
+ [PKG_CHECK_MODULES([OPENSSL], [openssl],
+ [AC_DEFINE([USE_OPENSSL], [1], [Use OpenSSL])])])])])
-PKG_CHECK_MODULES([LIBDANE], [gnutls-dane])
-AC_SUBST([LIBDANE_CFLAGS])
-AC_SUBST([LIBDANE_LIBS])
PKG_CHECK_MODULES([GSASL], [libgsasl])
-AC_SUBST(GSASL_CFLAGS)
-AC_SUBST(GSASL_LIBS)
PKG_CHECK_MODULES([UNBOUND], [libunbound])
-AC_SUBST(UNBOUND_CFLAGS)
-AC_SUBST(UNBOUND_LIBS)
AM_PATH_GPGME
PKG_CHECK_MODULES([ICU_I18N], [icu-i18n])
-AC_SUBST(ICU_I18N_CFLAGS)
-AC_SUBST(ICU_I18N_LIBS)
PKG_CHECK_MODULES([NETTLE], [nettle])
-AC_SUBST(NETTLE_CFLAGS)
-AC_SUBST(NETTLE_LIBS)
# Checks for header files.
diff --git a/src/Makefile.am b/src/Makefile.am
index ea6e1a5..deb3ac6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,15 +22,19 @@ librexmpp_la_SOURCES = rexmpp_roster.h rexmpp_roster.c \
rexmpp_socks.h rexmpp_socks.c \
rexmpp.h rexmpp.c \
rexmpp_dns.h rexmpp_dns.c \
+ rexmpp_tls.h rexmpp_tls.c \
rexmpp_jid.h rexmpp_jid.c \
rexmpp_openpgp.h rexmpp_openpgp.c \
rexmpp_console.h rexmpp_console.c \
rexmpp_pubsub.h rexmpp_pubsub.c
-include_HEADERS = rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
- rexmpp_dns.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h rexmpp_pubsub.h
-librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) \
- $(LIBDANE_CFLAGS) $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(GPGME_CFLAGS)
+include_HEADERS = config.h rexmpp_roster.h rexmpp_tcp.h rexmpp_socks.h rexmpp.h \
+ rexmpp_dns.h rexmpp_tls.h rexmpp_jid.h rexmpp_openpgp.h rexmpp_console.h \
+ rexmpp_pubsub.h
+librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) \
+ $(GNUTLS_CFLAGS) $(LIBDANE_CFLAGS) $(OPENSSL_CFLAGS) \
+ $(GSASL_CFLAGS) $(UNBOUND_CFLAGS) $(GPGME_CFLAGS)
$(ICU_I18N_CFLAGS) $(NETTLE_CFLAGS)
-librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(LIBDANE_LIBS) \
+librexmpp_la_LIBADD = $(LIBXML_LIBS) \
+ $(GNUTLS_LIBS) $(LIBDANE_LIBS) $(OPENSSL_LIBS) \
$(GSASL_LIBS) $(UNBOUND_LIBS) $(GPGME_LIBS) $(ICU_I18N_LIBS) \
$(NETTLE_LIBS)
diff --git a/src/rexmpp.c b/src/rexmpp.c
index 70ed886..36644f4 100644
--- a/src/rexmpp.c
+++ b/src/rexmpp.c
@@ -17,10 +17,6 @@
#include <libxml/tree.h>
#include <libxml/xmlsave.h>
-#include <gnutls/gnutls.h>
-#include <gnutls/crypto.h>
-#include <gnutls/x509.h>
-#include <gnutls/dane.h>
#include <gsasl.h>
#include <unbound.h>
#include <gpgme.h>
@@ -393,6 +389,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->nick_notifications = 1;
s->retrieve_openpgp_keys = 1;
s->autojoin_bookmarked_mucs = 1;
+ s->require_tls = 1;
s->send_buffer = NULL;
s->send_queue = NULL;
s->resolver_ctx = NULL;
@@ -413,8 +410,6 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
s->stanza_queue = NULL;
s->stream_id = NULL;
s->active_iq = NULL;
- s->tls_session_data = NULL;
- s->tls_session_data_size = 0;
s->reconnect_number = 0;
s->next_reconnect_time.tv_sec = 0;
s->next_reconnect_time.tv_usec = 0;
@@ -478,17 +473,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
ub_strerror(err));
}
- err = gnutls_certificate_allocate_credentials(&(s->gnutls_cred));
- if (err) {
- rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
- gnutls_strerror(err));
- xmlFreeParserCtxt(s->xml_parser);
- return REXMPP_E_TLS;
- }
- err = gnutls_certificate_set_x509_system_trust(s->gnutls_cred);
- if (err < 0) {
- rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s",
- gnutls_strerror(err));
+ if (rexmpp_tls_init(s)) {
xmlFreeParserCtxt(s->xml_parser);
return REXMPP_E_TLS;
}
@@ -497,7 +482,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
if (err) {
rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s",
gsasl_strerror(err));
- gnutls_certificate_free_credentials(s->gnutls_cred);
+ rexmpp_tls_deinit(s);
xmlFreeParserCtxt(s->xml_parser);
return REXMPP_E_SASL;
}
@@ -510,7 +495,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
rexmpp_log(s, LOG_CRIT, "gpgme initialisation error: %s",
gpgme_strerror(err));
gsasl_done(s->sasl_ctx);
- gnutls_certificate_free_credentials(s->gnutls_cred);
+ rexmpp_tls_deinit(s);
xmlFreeParserCtxt(s->xml_parser);
return REXMPP_E_PGP;
}
@@ -522,10 +507,7 @@ rexmpp_err_t rexmpp_init (rexmpp_t *s,
structures), but keeps others (e.g., stanza queue and stream ID,
since we may resume the stream afterwards). */
void rexmpp_cleanup (rexmpp_t *s) {
- if (s->tls_state != REXMPP_TLS_INACTIVE &&
- s->tls_state != REXMPP_TLS_AWAITING_DIRECT) {
- gnutls_deinit(s->gnutls_session);
- }
+ rexmpp_tls_cleanup(s);
s->tls_state = REXMPP_TLS_INACTIVE;
if (s->sasl_state != REXMPP_SASL_INACTIVE) {
gsasl_finish(s->sasl_session);
@@ -585,7 +567,7 @@ void rexmpp_done (rexmpp_t *s) {
rexmpp_cleanup(s);
gpgme_release(s->pgp_ctx);
gsasl_done(s->sasl_ctx);
- gnutls_certificate_free_credentials(s->gnutls_cred);
+ rexmpp_tls_deinit(s);
if (s->resolver_ctx != NULL) {
ub_ctx_delete(s->resolver_ctx);
s->resolver_ctx = NULL;
@@ -625,9 +607,6 @@ void rexmpp_done (rexmpp_t *s) {
free(s->active_iq);
s->active_iq = next;
}
- if (s->tls_session_data != NULL) {
- free(s->tls_session_data);
- }
}
void rexmpp_schedule_reconnect (rexmpp_t *s) {
@@ -638,7 +617,7 @@ void rexmpp_schedule_reconnect (rexmpp_t *s) {
return;
}
if (s->reconnect_number == 0) {
- gnutls_rnd(GNUTLS_RND_NONCE, &s->reconnect_seconds, sizeof(time_t));
+ gsasl_nonce((char*)&s->reconnect_seconds, sizeof(time_t));
if (s->reconnect_seconds < 0) {
s->reconnect_seconds = - s->reconnect_seconds;
}
@@ -822,12 +801,14 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s)
rexmpp_log(s, LOG_ERR, "nothing to send");
return REXMPP_E_SEND_BUFFER_EMPTY;
}
- int ret;
+ ssize_t ret;
+ rexmpp_tls_err_t err;
while (1) {
if (s->tls_state == REXMPP_TLS_ACTIVE) {
- ret = gnutls_record_send (s->gnutls_session,
- s->send_buffer,
- s->send_buffer_len);
+ err = rexmpp_tls_send (s,
+ s->send_buffer,
+ s->send_buffer_len,
+ &ret);
} else {
ret = send (s->server_socket,
s->send_buffer + s->send_buffer_sent,
@@ -856,10 +837,9 @@ rexmpp_err_t rexmpp_send_continue (rexmpp_t *s)
}
} else {
if (s->tls_state == REXMPP_TLS_ACTIVE) {
- if (ret != GNUTLS_E_AGAIN) {
+ if (err != REXMPP_TLS_E_AGAIN) {
s->tls_state = REXMPP_TLS_ERROR;
/* Assume a TCP error for now as well. */
- rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
@@ -1049,12 +1029,13 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) {
char chunk_raw[4096], *chunk;
ssize_t chunk_raw_len, chunk_len;
int sasl_err;
+ rexmpp_tls_err_t recv_err;
rexmpp_err_t err = REXMPP_SUCCESS;
/* Loop here in order to consume data from TLS buffers, which
wouldn't show up on select(). */
do {
if (s->tls_state == REXMPP_TLS_ACTIVE) {
- chunk_raw_len = gnutls_record_recv(s->gnutls_session, chunk_raw, 4096);
+ recv_err = rexmpp_tls_recv(s, chunk_raw, 4096, &chunk_raw_len);
} else {
chunk_raw_len = recv(s->server_socket, chunk_raw, 4096, 0);
}
@@ -1116,11 +1097,9 @@ rexmpp_err_t rexmpp_recv (rexmpp_t *s) {
}
} else {
if (s->tls_state == REXMPP_TLS_ACTIVE) {
- if (chunk_raw_len != GNUTLS_E_AGAIN) {
+ if (recv_err != REXMPP_TLS_E_AGAIN) {
s->tls_state = REXMPP_TLS_ERROR;
/* Assume a TCP error for now as well. */
- rexmpp_log(s, LOG_ERR, "TLS recv error: %s",
- gnutls_strerror(chunk_raw_len));
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_ERROR;
rexmpp_schedule_reconnect(s);
@@ -1226,97 +1205,18 @@ rexmpp_err_t rexmpp_try_next_host (rexmpp_t *s) {
return rexmpp_start_connecting(s);
}
-rexmpp_err_t rexmpp_tls_handshake (rexmpp_t *s) {
- s->tls_state = REXMPP_TLS_HANDSHAKE;
- int ret = gnutls_handshake(s->gnutls_session);
- if (ret == GNUTLS_E_AGAIN) {
- rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
- return REXMPP_E_AGAIN;
- } else if (ret == 0) {
- int status;
-
- int srv_is_secure = 0;
- if (s->stream_state == REXMPP_STREAM_NONE &&
- s->server_srv_tls != NULL) { /* Direct TLS */
- srv_is_secure = s->server_srv_tls->secure;
- } else if (s->stream_state != REXMPP_STREAM_NONE &&
- s->server_srv != NULL) { /* STARTTLS connection */
- srv_is_secure = s->server_srv->secure;
- }
-
- /* Check DANE TLSA records; experimental and purely informative
- now, but may be nice to (optionally) rely on it in the
- future. */
- if ((srv_is_secure || s->manual_host != NULL) &&
- s->server_socket_dns_secure) {
- /* Apparently GnuTLS only checks against the target
- server/derived host, while another possibility is a
- service/source host
- (<https://tools.ietf.org/html/rfc7712#section-5.1>,
- <https://tools.ietf.org/html/rfc7673#section-6>). */
- ret = dane_verify_session_crt(NULL, s->gnutls_session, s->server_host,
- "tcp", s->server_port, 0, 0, &status);
- if (ret) {
- rexmpp_log(s, LOG_WARNING, "DANE verification error: %s",
- dane_strerror(ret));
- } else if (status) {
- if (status & DANE_VERIFY_CA_CONSTRAINTS_VIOLATED) {
- rexmpp_log(s, LOG_WARNING, "The CA constraints were violated");
- }
- if (status & DANE_VERIFY_CERT_DIFFERS) {
- rexmpp_log(s, LOG_WARNING, "The certificate obtained via DNS differs");
- }
- if (status & DANE_VERIFY_UNKNOWN_DANE_INFO) {
- rexmpp_log(s, LOG_WARNING,
- "No known DANE data was found in the DNS record");
- }
- } else {
- rexmpp_log(s, LOG_INFO,
- "DANE verification did not reject the certificate");
- }
- }
-
- ret = gnutls_certificate_verify_peers3(s->gnutls_session,
- s->initial_jid.domain,
- &status);
- if (ret || status) {
- s->tls_state = REXMPP_TLS_ERROR;
- if (ret) {
- rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s",
- gnutls_strerror(ret));
- } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) {
- rexmpp_log(s, LOG_ERR, "Unexpected certificate owner");
- } else {
- rexmpp_log(s, LOG_ERR, "Untrusted certificate");
- }
- gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR);
- rexmpp_cleanup(s);
- rexmpp_schedule_reconnect(s);
- return REXMPP_E_TLS;
- }
+rexmpp_err_t
+rexmpp_process_tls_conn_err (rexmpp_t *s,
+ rexmpp_tls_err_t err)
+{
+ if (err == REXMPP_TLS_E_OTHER) {
+ s->tls_state = REXMPP_TLS_ERROR;
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ return REXMPP_E_TLS;
+ } else if (err == REXMPP_TLS_SUCCESS) {
+ rexmpp_log(s, LOG_DEBUG, "A TLS connection is established");
s->tls_state = REXMPP_TLS_ACTIVE;
- rexmpp_log(s, LOG_DEBUG, "TLS ready");
-
- if (gnutls_session_is_resumed(s->gnutls_session)) {
- rexmpp_log(s, LOG_INFO, "TLS session is resumed");
- } else {
- if (s->tls_session_data != NULL) {
- rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
- free(s->tls_session_data);
- s->tls_session_data = NULL;
- }
- gnutls_session_get_data(s->gnutls_session, NULL,
- &s->tls_session_data_size);
- s->tls_session_data = malloc(s->tls_session_data_size);
- ret = gnutls_session_get_data(s->gnutls_session, s->tls_session_data,
- &s->tls_session_data_size);
- if (ret != GNUTLS_E_SUCCESS) {
- rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
- gnutls_strerror(ret));
- return REXMPP_E_TLS;
- }
- }
-
if (s->stream_state == REXMPP_STREAM_NONE) {
/* It's a direct TLS connection, so open a stream after
connecting. */
@@ -1327,45 +1227,11 @@ rexmpp_err_t rexmpp_tls_handshake (rexmpp_t *s) {
return rexmpp_stream_open(s);
}
} else {
- rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
- gnutls_strerror(ret));
- rexmpp_cleanup(s);
- rexmpp_schedule_reconnect(s);
- return REXMPP_E_TLS;
+ s->tls_state = REXMPP_TLS_HANDSHAKE;
+ return REXMPP_E_AGAIN;
}
}
-rexmpp_err_t rexmpp_tls_start (rexmpp_t *s) {
- gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")};
- rexmpp_log(s, LOG_DEBUG, "starting TLS");
- gnutls_init(&s->gnutls_session, GNUTLS_CLIENT);
- gnutls_session_set_ptr(s->gnutls_session, s);
- gnutls_alpn_set_protocols(s->gnutls_session, &xmpp_client_protocol, 1, 0);
- gnutls_server_name_set(s->gnutls_session, GNUTLS_NAME_DNS,
- s->initial_jid.domain,
- strlen(s->initial_jid.domain));
- gnutls_set_default_priority(s->gnutls_session);
- gnutls_credentials_set(s->gnutls_session, GNUTLS_CRD_CERTIFICATE,
- s->gnutls_cred);
- gnutls_transport_set_int(s->gnutls_session, s->server_socket);
- gnutls_handshake_set_timeout(s->gnutls_session,
- GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
- if (s->tls_session_data != NULL) {
- int ret = gnutls_session_set_data(s->gnutls_session,
- s->tls_session_data,
- s->tls_session_data_size);
- if (ret != GNUTLS_E_SUCCESS) {
- rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s",
- gnutls_strerror(ret));
- free(s->tls_session_data);
- s->tls_session_data = NULL;
- s->tls_session_data_size = 0;
- }
- }
- s->tls_state = REXMPP_TLS_HANDSHAKE;
- return rexmpp_tls_handshake(s);
-}
-
rexmpp_err_t rexmpp_connected_to_server (rexmpp_t *s) {
s->tcp_state = REXMPP_TCP_CONNECTED;
rexmpp_log(s, LOG_INFO,
@@ -1374,7 +1240,7 @@ rexmpp_err_t rexmpp_connected_to_server (rexmpp_t *s) {
s->reconnect_number = 0;
xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) {
- return rexmpp_tls_start(s);
+ return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
} else {
return rexmpp_stream_open(s);
}
@@ -1665,6 +1531,120 @@ rexmpp_err_t rexmpp_stream_bind (rexmpp_t *s) {
rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
rexmpp_console_on_recv(s, elem);
+ /* Stream negotiation,
+ https://tools.ietf.org/html/rfc6120#section-4.3 */
+ if (s->stream_state == REXMPP_STREAM_NEGOTIATION) {
+ if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) {
+
+ /* Remember features. */
+ if (s->stream_features != NULL) {
+ xmlFreeNode(s->stream_features);
+ }
+ s->stream_features = xmlCopyNode(elem, 1);
+
+ /* TODO: check for required features properly here. Currently
+ assuming that STARTTLS, SASL, and BIND (with an exception for
+ SM) are always required if they are present. */
+ xmlNodePtr child =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls",
+ "starttls");
+ if (child != NULL) {
+ s->stream_state = REXMPP_STREAM_STARTTLS;
+ xmlNodePtr starttls_cmd = xmlNewNode(NULL, "starttls");
+ xmlNewNs(starttls_cmd, "urn:ietf:params:xml:ns:xmpp-tls", NULL);
+ rexmpp_send(s, starttls_cmd);
+ return REXMPP_SUCCESS;
+ } else if (s->require_tls && s->tls_state != REXMPP_TLS_ACTIVE) {
+ /* TLS is required, not established, and there's no such
+ feature available; fail here. */
+ rexmpp_log(s, LOG_ERR,
+ "TLS is required, but the server doesn't advertise such a feature");
+ return REXMPP_E_TLS;
+ }
+
+ /* Nothing to negotiate. */
+ if (xmlFirstElementChild(elem) == NULL) {
+ rexmpp_stream_is_ready(s);
+ return REXMPP_SUCCESS;
+ }
+
+ child = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanisms");
+ if (child != NULL) {
+ s->stream_state = REXMPP_STREAM_SASL;
+ s->sasl_state = REXMPP_SASL_NEGOTIATION;
+ char mech_list[2048]; /* todo: perhaps grow it dynamically */
+ mech_list[0] = '\0';
+ xmlNodePtr mechanism;
+ for (mechanism = xmlFirstElementChild(child);
+ mechanism != NULL;
+ mechanism = xmlNextElementSibling(mechanism)) {
+ if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanism")) {
+ char *mech_str = xmlNodeGetContent(mechanism);
+ snprintf(mech_list + strlen(mech_list),
+ 2048 - strlen(mech_list),
+ "%s ",
+ mech_str);
+ free(mech_str);
+ }
+ }
+ const char *mech =
+ gsasl_client_suggest_mechanism(s->sasl_ctx, mech_list);
+ rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech);
+ int sasl_err;
+ char *sasl_buf;
+ sasl_err = gsasl_client_start(s->sasl_ctx, mech, &(s->sasl_session));
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ sasl_err = gsasl_step64 (s->sasl_session, "", (char**)&sasl_buf);
+ if (sasl_err != GSASL_OK) {
+ if (sasl_err == GSASL_NEEDS_MORE) {
+ rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
+ } else {
+ rexmpp_log(s, LOG_ERR, "SASL error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ }
+ xmlNodePtr auth_cmd = xmlNewNode(NULL, "auth");
+ xmlNewProp(auth_cmd, "mechanism", mech);
+ xmlNewNs(auth_cmd, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
+ xmlNodeAddContent(auth_cmd, sasl_buf);
+ free(sasl_buf);
+ rexmpp_send(s, auth_cmd);
+ return REXMPP_SUCCESS;
+ }
+
+ child = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm");
+ if (s->stream_id != NULL && child != NULL) {
+ s->stream_state = REXMPP_STREAM_SM_RESUME;
+ char buf[11];
+ snprintf(buf, 11, "%u", s->stanzas_in_count);
+ xmlNodePtr sm_resume = xmlNewNode(NULL, "resume");
+ xmlNewNs(sm_resume, "urn:xmpp:sm:3", NULL);
+ xmlNewProp(sm_resume, "previd", s->stream_id);
+ xmlNewProp(sm_resume, "h", buf);
+ rexmpp_send(s, sm_resume);
+ return REXMPP_SUCCESS;
+ }
+
+ child =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind");
+ if (child != NULL) {
+ return rexmpp_stream_bind(s);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "Expected stream features, received %s", elem->name);
+ return REXMPP_E_STREAM;
+ }
+ }
+
/* IQs. These are the ones that should be processed by the library;
if a user-facing application wants to handle them on its own, it
should cancel further processing by the library (so we can send
@@ -1954,110 +1934,6 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
}
}
- /* Stream negotiation,
- https://tools.ietf.org/html/rfc6120#section-4.3 */
- if (s->stream_state == REXMPP_STREAM_NEGOTIATION &&
- rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) {
-
- /* Remember features. */
- if (s->stream_features != NULL) {
- xmlFreeNode(s->stream_features);
- }
- s->stream_features = xmlCopyNode(elem, 1);
-
- /* Nothing to negotiate. */
- if (xmlFirstElementChild(elem) == NULL) {
- rexmpp_stream_is_ready(s);
- return REXMPP_SUCCESS;
- }
-
- /* TODO: check for required features properly here. Currently
- assuming that STARTTLS, SASL, and BIND (with an exception for
- SM) are always required if they are present. */
- xmlNodePtr child =
- rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls",
- "starttls");
- if (child != NULL) {
- s->stream_state = REXMPP_STREAM_STARTTLS;
- xmlNodePtr starttls_cmd = xmlNewNode(NULL, "starttls");
- xmlNewNs(starttls_cmd, "urn:ietf:params:xml:ns:xmpp-tls", NULL);
- rexmpp_send(s, starttls_cmd);
- return REXMPP_SUCCESS;
- }
-
- child = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
- "mechanisms");
- if (child != NULL) {
- s->stream_state = REXMPP_STREAM_SASL;
- s->sasl_state = REXMPP_SASL_NEGOTIATION;
- char mech_list[2048]; /* todo: perhaps grow it dynamically */
- mech_list[0] = '\0';
- xmlNodePtr mechanism;
- for (mechanism = xmlFirstElementChild(child);
- mechanism != NULL;
- mechanism = xmlNextElementSibling(mechanism)) {
- if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl",
- "mechanism")) {
- char *mech_str = xmlNodeGetContent(mechanism);
- snprintf(mech_list + strlen(mech_list),
- 2048 - strlen(mech_list),
- "%s ",
- mech_str);
- free(mech_str);
- }
- }
- const char *mech =
- gsasl_client_suggest_mechanism(s->sasl_ctx, mech_list);
- rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech);
- int sasl_err;
- char *sasl_buf;
- sasl_err = gsasl_client_start(s->sasl_ctx, mech, &(s->sasl_session));
- if (sasl_err != GSASL_OK) {
- rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s",
- gsasl_strerror(sasl_err));
- s->sasl_state = REXMPP_SASL_ERROR;
- return REXMPP_E_SASL;
- }
- sasl_err = gsasl_step64 (s->sasl_session, "", (char**)&sasl_buf);
- if (sasl_err != GSASL_OK) {
- if (sasl_err == GSASL_NEEDS_MORE) {
- rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
- } else {
- rexmpp_log(s, LOG_ERR, "SASL error: %s",
- gsasl_strerror(sasl_err));
- s->sasl_state = REXMPP_SASL_ERROR;
- return REXMPP_E_SASL;
- }
- }
- xmlNodePtr auth_cmd = xmlNewNode(NULL, "auth");
- xmlNewProp(auth_cmd, "mechanism", mech);
- xmlNewNs(auth_cmd, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
- xmlNodeAddContent(auth_cmd, sasl_buf);
- free(sasl_buf);
- rexmpp_send(s, auth_cmd);
- return REXMPP_SUCCESS;
- }
-
- child = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm");
- if (s->stream_id != NULL && child != NULL) {
- s->stream_state = REXMPP_STREAM_SM_RESUME;
- char buf[11];
- snprintf(buf, 11, "%u", s->stanzas_in_count);
- xmlNodePtr sm_resume = xmlNewNode(NULL, "resume");
- xmlNewNs(sm_resume, "urn:xmpp:sm:3", NULL);
- xmlNewProp(sm_resume, "previd", s->stream_id);
- xmlNewProp(sm_resume, "h", buf);
- rexmpp_send(s, sm_resume);
- return REXMPP_SUCCESS;
- }
-
- child =
- rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind");
- if (child != NULL) {
- return rexmpp_stream_bind(s);
- }
- }
-
/* Stream errors, https://tools.ietf.org/html/rfc6120#section-4.9 */
if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams",
"error")) {
@@ -2080,7 +1956,7 @@ rexmpp_err_t rexmpp_process_element (rexmpp_t *s, xmlNodePtr elem) {
if (s->stream_state == REXMPP_STREAM_STARTTLS) {
if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
"proceed")) {
- return rexmpp_tls_start(s);
+ return rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
} else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
"failure")) {
rexmpp_log(s, LOG_ERR, "STARTTLS failure");
@@ -2505,7 +2381,7 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
this, if everything goes well. */
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
s->tls_state == REXMPP_TLS_HANDSHAKE) {
- rexmpp_err_t err = rexmpp_tls_handshake(s);
+ rexmpp_err_t err = rexmpp_process_tls_conn_err(s, rexmpp_tls_connect(s));
if (err > REXMPP_E_AGAIN) {
return err;
}
@@ -2527,14 +2403,13 @@ rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
if (s->tcp_state == REXMPP_TCP_CONNECTED &&
s->stream_state == REXMPP_STREAM_CLOSED &&
s->tls_state == REXMPP_TLS_CLOSING) {
- int ret = gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR);
- if (ret == GNUTLS_E_SUCCESS) {
+ rexmpp_tls_err_t err = rexmpp_tls_disconnect(s);
+ if (err == REXMPP_TLS_SUCCESS) {
s->tls_state = REXMPP_TLS_INACTIVE;
rexmpp_cleanup(s);
s->tcp_state = REXMPP_TCP_CLOSED;
} else {
- rexmpp_log(s, LOG_WARNING, "Failed to close TLS connection: %s",
- gnutls_strerror(ret));
+ s->tls_state = REXMPP_TLS_ERROR;
return REXMPP_E_TLS;
}
}
@@ -2550,7 +2425,7 @@ 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, max_fd = 0;
+ int conn_fd, tls_fd, max_fd = 0;
if (s->resolver_state != REXMPP_RESOLVER_NONE &&
s->resolver_state != REXMPP_RESOLVER_READY) {
@@ -2579,13 +2454,9 @@ int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
}
if (s->tls_state == REXMPP_TLS_HANDSHAKE) {
- if (gnutls_record_get_direction(s->gnutls_session) == 0) {
- FD_SET(s->server_socket, read_fds);
- } else {
- FD_SET(s->server_socket, write_fds);
- }
- if (s->server_socket + 1 > max_fd) {
- max_fd = s->server_socket + 1;
+ tls_fd = rexmpp_tls_fds(s, read_fds, write_fds);
+ if (tls_fd > max_fd) {
+ max_fd = tls_fd;
}
}
diff --git a/src/rexmpp.h b/src/rexmpp.h
index 93740d8..b47703f 100644
--- a/src/rexmpp.h
+++ b/src/rexmpp.h
@@ -11,17 +11,18 @@
#include <stdint.h>
#include <unbound.h>
-#include <gnutls/gnutls.h>
#include <gsasl.h>
#include <libxml/tree.h>
#include <gpgme.h>
+
+typedef struct rexmpp rexmpp_t;
+
#include "rexmpp_tcp.h"
#include "rexmpp_socks.h"
#include "rexmpp_dns.h"
+#include "rexmpp_tls.h"
#include "rexmpp_jid.h"
-typedef struct rexmpp rexmpp_t;
-
/**
@brief An info/query callback function type.
@param[in,out] s A ::rexmpp structure.
@@ -253,6 +254,7 @@ struct rexmpp
int nick_notifications; /* XEP-0172 */
int retrieve_openpgp_keys; /* XEP-0373 */
int autojoin_bookmarked_mucs; /* XEP-0402 */
+ int require_tls;
/* Resource limits. */
uint32_t stanza_queue_size;
@@ -338,10 +340,7 @@ struct rexmpp
xmlNodePtr input_queue_last;
/* TLS structures. */
- void *tls_session_data;
- size_t tls_session_data_size;
- gnutls_session_t gnutls_session;
- gnutls_certificate_credentials_t gnutls_cred;
+ rexmpp_tls_t tls;
/* SASL structures. */
Gsasl *sasl_ctx;
diff --git a/src/rexmpp_tls.c b/src/rexmpp_tls.c
new file mode 100644
index 0000000..bd464ce
--- /dev/null
+++ b/src/rexmpp_tls.c
@@ -0,0 +1,387 @@
+/**
+ @file rexmpp_tls.c
+ @brief TLS abstraction
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+*/
+
+#include <syslog.h>
+#include <string.h>
+
+#include "config.h"
+
+#if defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include <gnutls/x509.h>
+#include <gnutls/dane.h>
+#elif defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+#endif
+
+#include "rexmpp.h"
+#include "rexmpp_tls.h"
+
+#if defined(USE_OPENSSL)
+rexmpp_tls_err_t rexmpp_process_openssl_ret (rexmpp_t *s, int ret) {
+ int err = SSL_get_error(s->tls.openssl_conn, ret);
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+ if (ret == 1) {
+ return REXMPP_TLS_SUCCESS;
+ } else if (err == SSL_ERROR_WANT_READ) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else if (err == SSL_ERROR_WANT_WRITE) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_WRITE;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "OpenSSL error %d", err);
+ return REXMPP_TLS_E_OTHER;
+ }
+}
+#endif
+
+int rexmpp_tls_init (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ int err;
+ s->tls.tls_session_data = NULL;
+ s->tls.tls_session_data_size = 0;
+
+ err = gnutls_certificate_allocate_credentials(&(s->tls.gnutls_cred));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
+ gnutls_strerror(err));
+ return 1;
+ }
+ err = gnutls_certificate_set_x509_system_trust(s->tls.gnutls_cred);
+ if (err < 0) {
+ rexmpp_log(s, LOG_CRIT, "Certificates loading error: %s",
+ gnutls_strerror(err));
+ return 1;
+ }
+ return 0;
+#elif defined(USE_OPENSSL)
+ SSL_library_init();
+ SSL_load_error_strings();
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+ s->tls.openssl_conn = NULL;
+ s->tls.openssl_ctx = SSL_CTX_new(TLS_client_method());
+ if (s->tls.openssl_ctx == NULL) {
+ rexmpp_log(s, LOG_CRIT, "OpenSSL context creation error");
+ return 1;
+ }
+ SSL_CTX_set_verify(s->tls.openssl_ctx, SSL_VERIFY_PEER, NULL);
+ if (SSL_CTX_set_default_verify_paths(s->tls.openssl_ctx) == 0) {
+ rexmpp_log(s, LOG_CRIT, "Failed to set default verify paths for OpenSSL context");
+ SSL_CTX_free(s->tls.openssl_ctx);
+ s->tls.openssl_ctx = NULL;
+ return 1;
+ }
+ return 0;
+#else
+ (void)s;
+ return 0;
+#endif
+}
+
+
+void rexmpp_tls_cleanup (rexmpp_t *s) {
+ if (s->tls_state != REXMPP_TLS_INACTIVE &&
+ s->tls_state != REXMPP_TLS_AWAITING_DIRECT) {
+#if defined(USE_GNUTLS)
+ gnutls_deinit(s->tls.gnutls_session);
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_conn != NULL) {
+ SSL_free(s->tls.openssl_conn);
+ s->tls.openssl_conn = NULL;
+ }
+ s->tls.openssl_direction = REXMPP_OPENSSL_NONE;
+#else
+ (void)s;
+#endif
+ }
+}
+
+void rexmpp_tls_deinit (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ gnutls_certificate_free_credentials(s->tls.gnutls_cred);
+ if (s->tls.tls_session_data != NULL) {
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ }
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_ctx != NULL) {
+ SSL_CTX_free(s->tls.openssl_ctx);
+ }
+ s->tls.openssl_ctx = NULL;
+#else
+ (void)s;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_connect (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")};
+ rexmpp_log(s, LOG_DEBUG, "starting TLS");
+ gnutls_init(&s->tls.gnutls_session, GNUTLS_CLIENT);
+ gnutls_session_set_ptr(s->tls.gnutls_session, s);
+ gnutls_alpn_set_protocols(s->tls.gnutls_session, &xmpp_client_protocol, 1, 0);
+ gnutls_server_name_set(s->tls.gnutls_session, GNUTLS_NAME_DNS,
+ s->initial_jid.domain,
+ strlen(s->initial_jid.domain));
+ gnutls_set_default_priority(s->tls.gnutls_session);
+ gnutls_credentials_set(s->tls.gnutls_session, GNUTLS_CRD_CERTIFICATE,
+ s->tls.gnutls_cred);
+ gnutls_transport_set_int(s->tls.gnutls_session, s->server_socket);
+ gnutls_handshake_set_timeout(s->tls.gnutls_session,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ if (s->tls.tls_session_data != NULL) {
+ int ret = gnutls_session_set_data(s->tls.gnutls_session,
+ s->tls.tls_session_data,
+ s->tls.tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_WARNING, "Failed to set TLS session data: %s",
+ gnutls_strerror(ret));
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ s->tls.tls_session_data_size = 0;
+ }
+ }
+ }
+
+ int ret = gnutls_handshake(s->tls.gnutls_session);
+ if (ret == GNUTLS_E_AGAIN) {
+ rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
+ return REXMPP_TLS_E_AGAIN;
+ } else if (ret == 0) {
+ int status;
+
+ int srv_is_secure = 0;
+ if (s->stream_state == REXMPP_STREAM_NONE &&
+ s->server_srv_tls != NULL) { /* Direct TLS */
+ srv_is_secure = s->server_srv_tls->secure;
+ } else if (s->stream_state != REXMPP_STREAM_NONE &&
+ s->server_srv != NULL) { /* STARTTLS connection */
+ srv_is_secure = s->server_srv->secure;
+ }
+
+ /* Check DANE TLSA records; experimental and purely informative
+ now, but may be nice to (optionally) rely on it in the
+ future. */
+ if ((srv_is_secure || s->manual_host != NULL) &&
+ s->server_socket_dns_secure) {
+ /* Apparently GnuTLS only checks against the target
+ server/derived host, while another possibility is a
+ service/source host
+ (<https://tools.ietf.org/html/rfc7712#section-5.1>,
+ <https://tools.ietf.org/html/rfc7673#section-6>). */
+ ret = dane_verify_session_crt(NULL, s->tls.gnutls_session, s->server_host,
+ "tcp", s->server_port, 0, 0, &status);
+ if (ret) {
+ rexmpp_log(s, LOG_WARNING, "DANE verification error: %s",
+ dane_strerror(ret));
+ } else if (status) {
+ if (status & DANE_VERIFY_CA_CONSTRAINTS_VIOLATED) {
+ rexmpp_log(s, LOG_WARNING, "The CA constraints were violated");
+ }
+ if (status & DANE_VERIFY_CERT_DIFFERS) {
+ rexmpp_log(s, LOG_WARNING, "The certificate obtained via DNS differs");
+ }
+ if (status & DANE_VERIFY_UNKNOWN_DANE_INFO) {
+ rexmpp_log(s, LOG_WARNING,
+ "No known DANE data was found in the DNS record");
+ }
+ } else {
+ rexmpp_log(s, LOG_INFO,
+ "DANE verification did not reject the certificate");
+ }
+ }
+
+ ret = gnutls_certificate_verify_peers3(s->tls.gnutls_session,
+ s->initial_jid.domain,
+ &status);
+ if (ret || status) {
+ if (ret) {
+ rexmpp_log(s, LOG_ERR, "Certificate parsing error: %s",
+ gnutls_strerror(ret));
+ } else if (status & GNUTLS_CERT_UNEXPECTED_OWNER) {
+ rexmpp_log(s, LOG_ERR, "Unexpected certificate owner");
+ } else {
+ rexmpp_log(s, LOG_ERR, "Untrusted certificate");
+ }
+ gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR);
+ return REXMPP_TLS_E_OTHER;
+ }
+
+ if (gnutls_session_is_resumed(s->tls.gnutls_session)) {
+ rexmpp_log(s, LOG_INFO, "TLS session is resumed");
+ } else {
+ if (s->tls.tls_session_data != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
+ free(s->tls.tls_session_data);
+ s->tls.tls_session_data = NULL;
+ }
+ gnutls_session_get_data(s->tls.gnutls_session, NULL,
+ &s->tls.tls_session_data_size);
+ s->tls.tls_session_data = malloc(s->tls.tls_session_data_size);
+ ret = gnutls_session_get_data(s->tls.gnutls_session, s->tls.tls_session_data,
+ &s->tls.tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ if (s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ s->tls.openssl_conn = SSL_new(s->tls.openssl_ctx);
+ if (s->tls.openssl_conn == NULL) {
+ rexmpp_log(s, LOG_ERR, "Failed to create an OpenSSL connection object");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set_fd(s->tls.openssl_conn, s->server_socket) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a file descriptor for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ if (SSL_set1_host(s->tls.openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ /* For SNI */
+ if (SSL_set_tlsext_host_name(s->tls.openssl_conn, s->initial_jid.domain) == 0) {
+ rexmpp_log(s, LOG_ERR, "Failed to set a tlsext hostname for OpenSSL connection");
+ return REXMPP_TLS_E_OTHER;
+ }
+ }
+ return rexmpp_process_openssl_ret(s, SSL_connect(s->tls.openssl_conn));
+#else
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_disconnect (rexmpp_t *s) {
+#if defined(USE_GNUTLS)
+ int ret = gnutls_bye(s->tls.gnutls_session, GNUTLS_SHUT_RDWR);
+ if (ret == GNUTLS_E_SUCCESS) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to close TLS connection: %s",
+ gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ int ret = SSL_shutdown(s->tls.openssl_conn);
+ if (ret == 0) {
+ s->tls.openssl_direction = REXMPP_OPENSSL_READ;
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_send (rexmpp_t *s, void *data, size_t data_size, ssize_t *written)
+{
+#if defined(USE_GNUTLS)
+ *written = -1;
+ ssize_t ret = gnutls_record_send(s->tls.gnutls_session,
+ data,
+ data_size);
+ if (ret >= 0) {
+ *written = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ *written = -1;
+ int ret = SSL_write_ex(s->tls.openssl_conn, data, data_size, written);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)written;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+rexmpp_tls_err_t
+rexmpp_tls_recv (rexmpp_t *s, void *data, size_t data_size, ssize_t *received) {
+#if defined(USE_GNUTLS)
+ *received = -1;
+ ssize_t ret = gnutls_record_recv(s->tls.gnutls_session, data, data_size);
+ if (ret >= 0) {
+ *received = ret;
+ return REXMPP_TLS_SUCCESS;
+ } else if (ret == GNUTLS_E_AGAIN) {
+ return REXMPP_TLS_E_AGAIN;
+ } else {
+ rexmpp_log(s, LOG_ERR, "TLS recv error: %s", gnutls_strerror(ret));
+ return REXMPP_TLS_E_OTHER;
+ }
+#elif defined(USE_OPENSSL)
+ *received = -1;
+ int ret = SSL_read_ex(s->tls.openssl_conn, data, data_size, received);
+ if (ret > 0) {
+ return REXMPP_TLS_SUCCESS;
+ } else {
+ return rexmpp_process_openssl_ret(s, ret);
+ }
+#else
+ (void)data;
+ (void)data_size;
+ (void)received;
+ rexmpp_log(s, LOG_ERR, "rexmpp is compiled without TLS support");
+ return REXMPP_TLS_E_OTHER;
+#endif
+}
+
+int rexmpp_tls_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+#if defined(USE_GNUTLS)
+ if (gnutls_record_get_direction(s->tls.gnutls_session) == 0) {
+ FD_SET(s->server_socket, read_fds);
+ } else {
+ FD_SET(s->server_socket, write_fds);
+ }
+ return s->server_socket + 1;
+#elif defined(USE_OPENSSL)
+ if (s->tls.openssl_direction == REXMPP_OPENSSL_READ) {
+ FD_SET(s->server_socket, read_fds);
+ return s->server_socket + 1;
+ }
+ if (s->tls.openssl_direction == REXMPP_OPENSSL_WRITE) {
+ FD_SET(s->server_socket, write_fds);
+ return s->server_socket + 1;
+ }
+ return 0;
+#else
+ (void)s;
+ (void)read_fds;
+ (void)write_fds;
+ return 0;
+#endif
+}
diff --git a/src/rexmpp_tls.h b/src/rexmpp_tls.h
new file mode 100644
index 0000000..22515e3
--- /dev/null
+++ b/src/rexmpp_tls.h
@@ -0,0 +1,74 @@
+/**
+ @file rexmpp_tls.h
+ @brief TLS abstraction
+ @author defanor <defanor@uberspace.net>
+ @date 2021
+ @copyright MIT license.
+
+These functions only alter the rexmpp structure's tls member (in
+particular, they don't change other state variables), but use rexmpp_t
+to write logs and read other values (including server socket).
+
+*/
+
+
+#ifndef REXMPP_TLS_H
+#define REXMPP_TLS_H
+
+#include <stdint.h>
+
+#include "rexmpp.h"
+#include "config.h"
+
+typedef struct rexmpp_tls rexmpp_tls_t;
+
+/**
+ @brief TLS operation results.
+*/
+enum rexmpp_tls_err {
+ REXMPP_TLS_SUCCESS,
+ REXMPP_TLS_E_AGAIN,
+ REXMPP_TLS_E_OTHER
+};
+
+typedef enum rexmpp_tls_err rexmpp_tls_err_t;
+
+#if defined(USE_GNUTLS)
+#include <gnutls/gnutls.h>
+struct rexmpp_tls {
+ void *tls_session_data;
+ size_t tls_session_data_size;
+ gnutls_session_t gnutls_session;
+ gnutls_certificate_credentials_t gnutls_cred;
+};
+#elif defined(USE_OPENSSL)
+#include <openssl/ssl.h>
+enum rexmpp_openssl_direction {
+ REXMPP_OPENSSL_NONE,
+ REXMPP_OPENSSL_READ,
+ REXMPP_OPENSSL_WRITE
+};
+struct rexmpp_tls {
+ SSL_CTX *openssl_ctx;
+ SSL *openssl_conn;
+ enum rexmpp_openssl_direction openssl_direction;
+};
+#else
+struct rexmpp_tls {
+ int dummy;
+};
+#endif
+
+int rexmpp_tls_init(rexmpp_t *s);
+void rexmpp_tls_cleanup(rexmpp_t *s);
+void rexmpp_tls_deinit(rexmpp_t *s);
+
+rexmpp_tls_err_t rexmpp_tls_connect(rexmpp_t *s);
+rexmpp_tls_err_t rexmpp_tls_disconnect(rexmpp_t *s);
+
+rexmpp_tls_err_t rexmpp_tls_send(rexmpp_t *s, void *data, size_t data_size, ssize_t *written);
+rexmpp_tls_err_t rexmpp_tls_recv(rexmpp_t *s, void *data, size_t data_size, ssize_t *received);
+
+int rexmpp_tls_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+#endif