summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
5 files changed, 630 insertions, 295 deletions
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