From f45380bb2354344df16aff51b98cbaa0ac06425a Mon Sep 17 00:00:00 2001 From: James Canete Date: Wed, 5 Sep 2007 20:01:24 +0000 Subject: [PATCH] Added OpenSSL support. Added win32-specific srv lookup support. Extend session timeout to 15 seconds. --- src/auth.c | 88 ++++++++++++++++-- src/common.h | 2 + src/conn.c | 15 +++- src/ctx.c | 2 + src/event.c | 59 ++++++++---- src/sock.c | 52 +++++++++++ src/sock.h | 4 + src/stanza.c | 41 +++++++++ src/tls.h | 1 - src/tls_openssl.c | 223 ++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 463 insertions(+), 24 deletions(-) create mode 100644 src/tls_openssl.c diff --git a/src/auth.c b/src/auth.c index 38c574f..8ae6e47 100644 --- a/src/auth.c +++ b/src/auth.c @@ -27,10 +27,11 @@ /* FIXME: these should be configurable */ #define FEATURES_TIMEOUT 2000 /* 2 seconds */ #define BIND_TIMEOUT 2000 /* 2 seconds */ -#define SESSION_TIMEOUT 2000 /* 2 seconds */ +#define SESSION_TIMEOUT 15000 /* 15 seconds */ #define LEGACY_TIMEOUT 2000 /* 2 seconds */ static void _auth(xmpp_conn_t * const conn); +static void _handle_open_tls(xmpp_conn_t * const conn); static void _handle_open_sasl(xmpp_conn_t * const conn); static int _handle_missing_legacy(xmpp_conn_t * const conn, void * const userdata); @@ -89,8 +90,13 @@ static int _handle_error(xmpp_conn_t * const conn, if (conn->stream_error) { child = xmpp_stanza_get_children(stanza); do { - if (child && strcmp(xmpp_stanza_get_ns(child), - XMPP_NS_STREAMS_IETF) == 0) { + char *ns = NULL; + + if (child) { + ns = xmpp_stanza_get_ns(child); + } + + if (ns && strcmp(ns, XMPP_NS_STREAMS_IETF) == 0) { name = xmpp_stanza_get_name(child); if (strcmp(name, "text") == 0) { if (conn->stream_error->text) @@ -203,7 +209,7 @@ static int _handle_features(xmpp_conn_t * const conn, /* TODO: Implement TLS */ _auth(conn); - + return 0; } @@ -222,6 +228,36 @@ static char *_get_authid(xmpp_conn_t * const conn) return authid; } +static int _handle_proceedtls_default(xmpp_conn_t * const conn, + xmpp_stanza_t * const stanza, + void * const userdata) +{ + char *name; + name = xmpp_stanza_get_name(stanza); + xmpp_debug(conn->ctx, "xmpp", + "handle proceedtls called for %s", name); + + if (strcmp(name, "proceed") == 0) { + xmpp_debug(conn->ctx, "xmpp", "proceeding with TLS"); + + parser_prepare_reset(conn, _handle_open_tls); + + conn->tls = tls_new(conn->ctx, conn->sock); + + if (!tls_start(conn->tls)) + { + xmpp_debug(conn->ctx, "xmpp", "Couldn't start TLS!"); + } + else + { + conn_open_stream(conn); + } + } + + return 0; +} + + static int _handle_digestmd5_default(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza, void * const userdata) @@ -393,6 +429,20 @@ static int _handle_plain(xmpp_conn_t * const conn, return ret; } +static xmpp_stanza_t *_make_starttls(xmpp_conn_t * const conn) +{ + xmpp_stanza_t *starttls; + + /* build start stanza */ + starttls = xmpp_stanza_new(conn->ctx); + if (starttls) { + xmpp_stanza_set_name(starttls, "starttls"); + xmpp_stanza_set_ns(starttls, XMPP_NS_TLS); + } + + return starttls; +} + static xmpp_stanza_t *_make_sasl_auth(xmpp_conn_t * const conn, const char * const mechanism) { @@ -418,7 +468,25 @@ static void _auth(xmpp_conn_t * const conn) xmpp_stanza_t *auth, *authdata, *query, *child, *iq; char *str, *authid; - if (conn->sasl_support & SASL_MASK_DIGESTMD5) { + if (conn->tls_support) + { + auth = _make_starttls(conn); + + if (!auth) { + disconnect_mem_error(conn); + return; + } + + handler_add(conn, _handle_proceedtls_default, + XMPP_NS_TLS, NULL, NULL, NULL); + + xmpp_send(conn, auth); + xmpp_stanza_release(auth); + + /* TLS was tried, unset flag */ + conn->tls_support = 0; + } + else if (conn->sasl_support & SASL_MASK_DIGESTMD5) { auth = _make_sasl_auth(conn, "DIGEST-MD5"); if (!auth) { disconnect_mem_error(conn); @@ -592,6 +660,16 @@ void auth_handle_open(xmpp_conn_t * const conn) FEATURES_TIMEOUT, NULL); } +/* called when stream:stream tag received after TLS connection */ +static void _handle_open_tls(xmpp_conn_t * const conn) +{ + xmpp_debug(conn->ctx, "xmpp", "TLS successful, proceeding with SASL"); + + /* go straight to SASL auth */ + _auth(conn); +} + + /* called when stream:stream tag received after SASL auth */ static void _handle_open_sasl(xmpp_conn_t * const conn) { diff --git a/src/common.h b/src/common.h index 8b45018..24ecb21 100644 --- a/src/common.h +++ b/src/common.h @@ -24,6 +24,7 @@ #include "strophe.h" #include "sock.h" +#include "tls.h" #include "hash.h" #include "util.h" @@ -157,6 +158,7 @@ struct _xmpp_conn_t { int error; xmpp_stream_error_t *stream_error; sock_t sock; + tls_t *tls; int tls_support; int sasl_support; /* if true, field is a bitfield of supported diff --git a/src/conn.c b/src/conn.c index 9077e22..4b52793 100644 --- a/src/conn.c +++ b/src/conn.c @@ -41,6 +41,7 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t * const ctx) conn->type = XMPP_UNKNOWN; conn->sock = -1; + conn->tls = NULL; conn->timeout_stamp = 0; conn->error = 0; conn->stream_error = NULL; @@ -241,6 +242,9 @@ int xmpp_connect_client(xmpp_conn_t * const conn, xmpp_conn_handler callback, void * const userdata) { + char connectdomain[2048]; + int connectport; + conn->type = XMPP_CLIENT; if (domain) { @@ -250,8 +254,8 @@ int xmpp_connect_client(xmpp_conn_t * const conn, } if (!conn->domain) return -1; - /* TODO: look up SRV record for actual host and port */ - conn->sock = sock_connect(conn->domain, 5222); + sock_srv_lookup("xmpp-client", "tcp", conn->domain, connectdomain, 2048, &connectport); + conn->sock = sock_connect(connectdomain, connectport); if (conn->sock < 0) return -1; /* setup handler */ @@ -265,7 +269,7 @@ int xmpp_connect_client(xmpp_conn_t * const conn, conn->state = XMPP_STATE_CONNECTING; conn->timeout_stamp = time_stamp(); - xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", conn->domain); + xmpp_debug(conn->ctx, "xmpp", "attempting to connect to %s", connectdomain); return 0; } @@ -285,6 +289,11 @@ void conn_disconnect(xmpp_conn_t * const conn) { xmpp_debug(conn->ctx, "xmpp", "Closing socket."); conn->state = XMPP_STATE_DISCONNECTED; + if (conn->tls) { + tls_stop(conn->tls); + tls_free(conn->tls); + conn->tls = NULL; + } sock_close(conn->sock); /* fire off connection handler */ diff --git a/src/ctx.c b/src/ctx.c index 3250b0d..61bdbae 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -26,11 +26,13 @@ void xmpp_initialize(void) { sock_initialize(); + tls_initialize(); } void xmpp_shutdown(void) { sock_shutdown(); + tls_shutdown(); } /** version **/ diff --git a/src/event.c b/src/event.c index 05b05f6..a0f6e59 100644 --- a/src/event.c +++ b/src/event.c @@ -61,16 +61,31 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) sq = conn->send_queue_head; while (sq) { towrite = sq->len - sq->written; - ret = sock_write(conn->sock, &sq->data[sq->written], towrite); - if (ret < 0 && !sock_is_recoverable(sock_error())) { - /* an error occured */ - conn->error = sock_error(); - break; - } else if (ret < towrite) { - /* not all data could be sent now */ - if (ret >= 0) sq->written += ret; - break; + if (conn->tls) { + ret = tls_write(conn->tls, &sq->data[sq->written], towrite); + + if (ret < 0 && !tls_is_recoverable(tls_error(conn->tls))) { + /* an error occured */ + conn->error = tls_error(conn->tls); + break; + } else if (ret < towrite) { + /* not all data could be sent now */ + if (ret >= 0) sq->written += ret; + break; + } + } else { + ret = sock_write(conn->sock, &sq->data[sq->written], towrite); + + if (ret < 0 && !sock_is_recoverable(sock_error())) { + /* an error occured */ + conn->error = sock_error(); + break; + } else if (ret < towrite) { + /* not all data could be sent now */ + if (ret >= 0) sq->written += ret; + break; + } } /* all data for this queue item written, delete and move on */ @@ -161,7 +176,7 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) /* no events happened */ if (ret == 0) return; - + /* process events */ connitem = ctx->connlist; while (connitem) { @@ -191,7 +206,12 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) break; case XMPP_STATE_CONNECTED: if (FD_ISSET(conn->sock, &rfds)) { - ret = sock_read(conn->sock, buf, 4096); + if (conn->tls) { + ret = tls_read(conn->tls, buf, 4096); + } else { + ret = sock_read(conn->sock, buf, 4096); + } + if (ret > 0) { ret = XML_Parse(conn->parser, buf, ret, 0); if (!ret) { @@ -201,10 +221,19 @@ void xmpp_run_once(xmpp_ctx_t *ctx, const unsigned long timeout) conn_disconnect(conn); } } else { - /* return of 0 means socket closed by server */ - xmpp_debug(ctx, "xmpp", "Socket closed by remote host."); - conn->error = ECONNRESET; - conn_disconnect(conn); + if (conn->tls) { + if (!tls_is_recoverable(tls_error(conn->tls))) + { + xmpp_debug(ctx, "xmpp", "Unrecoverable TLS error."); + conn->error = tls_error(conn->tls); + conn_disconnect(conn); + } + } else { + /* return of 0 means socket closed by server */ + xmpp_debug(ctx, "xmpp", "Socket closed by remote host."); + conn->error = ECONNRESET; + conn_disconnect(conn); + } } } diff --git a/src/sock.c b/src/sock.c index 811d779..92b3270 100644 --- a/src/sock.c +++ b/src/sock.c @@ -19,6 +19,7 @@ #ifdef _WIN32 #include #include +#include #define snprintf _snprintf #else #include @@ -163,3 +164,54 @@ int sock_connect_error(const sock_t sock) if (ret < 0) return ret; return error; } + +void sock_srv_lookup(const char *service, const char *proto, const char *domain, char *resulttarget, int resulttargetlength, int *resultport) +{ + int set = 0; + char fulldomain[2048]; + + snprintf(fulldomain, 2048, "_%s._%s.%s", service, proto, domain); +#ifdef _WIN32 + { + HINSTANCE hdnsapi = NULL; + + DNS_STATUS (WINAPI * pDnsQuery_A)(PCSTR, WORD, DWORD, PIP4_ARRAY, PDNS_RECORD*, PVOID*); + void (WINAPI * pDnsRecordListFree)(PDNS_RECORD, DNS_FREE_TYPE); + + if (hdnsapi = LoadLibrary("dnsapi.dll")) { + pDnsQuery_A = GetProcAddress(hdnsapi, "DnsQuery_A"); + pDnsRecordListFree = GetProcAddress(hdnsapi, "DnsRecordListFree"); + + if (pDnsQuery_A && pDnsRecordListFree) { + PDNS_RECORD dnsrecords = NULL; + + if (pDnsQuery_A(fulldomain, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &dnsrecords, NULL) == 0) { + PDNS_RECORD current = dnsrecords; + + while (current) { + if (current->wType == DNS_TYPE_SRV) { + snprintf(resulttarget, resulttargetlength, current->Data.Srv.pNameTarget); + *resultport = current->Data.Srv.wPort; + set = 1; + + current = NULL; + } else { + current = current->pNext; + } + } + } + + pDnsRecordListFree(dnsrecords, DnsFreeRecordList); + } + /*UnloadLibrary(hdnsapi);*/ + } + } +#else +#endif + + if (!set) + { + snprintf(resulttarget, resulttargetlength, domain); + *resultport = 5222; + } +} \ No newline at end of file diff --git a/src/sock.h b/src/sock.h index 5897b23..542b753 100644 --- a/src/sock.h +++ b/src/sock.h @@ -40,4 +40,8 @@ int sock_is_recoverable(const int error); /* checks for an error after connect, return 0 if connect successful */ int sock_connect_error(const sock_t sock); +void sock_srv_lookup(const char *service, const char *proto, + const char *domain, char *resulttarget, + int resulttargetlength, int *resultport); + #endif /* __LIBSTROPHE_SOCK_H__ */ diff --git a/src/stanza.c b/src/stanza.c index 0f658c2..cb93af2 100644 --- a/src/stanza.c +++ b/src/stanza.c @@ -335,6 +335,47 @@ int xmpp_stanza_set_attributes(xmpp_stanza_t * const stanza, return XMPP_EOK; } +int xmpp_stanza_get_attribute_count(xmpp_stanza_t * const stanza) +{ + if (stanza->attributes == NULL) { + return 0; + } + + return hash_num_keys(stanza->attributes); +} + +int xmpp_stanza_get_attributes(xmpp_stanza_t * const stanza, + char **attr, int attrlen) +{ + hash_iterator_t *iter; + char *key; + int num = 0; + + if (stanza->attributes == NULL) { + return 0; + } + + iter = hash_iter_new(stanza->attributes); + while ((key = hash_iter_next(iter)) != NULL && attrlen) { + attr[num++] = key; + attrlen--; + if (attrlen == 0) { + hash_iter_release(iter); + return num; + } + attr[num++] = hash_get(stanza->attributes, key); + attrlen--; + if (attrlen == 0) { + hash_iter_release(iter); + return num; + } + } + + hash_iter_release(iter); + return num; +} + + int xmpp_stanza_set_attribute(xmpp_stanza_t * const stanza, const char * const key, const char * const value) diff --git a/src/tls.h b/src/tls.h index 1814ee5..ca8eb8e 100644 --- a/src/tls.h +++ b/src/tls.h @@ -15,7 +15,6 @@ #ifndef __LIBSTROPHE_TLS_H__ #define __LIBSTROPHE_TLS_H__ -#include #include "common.h" #include "sock.h" diff --git a/src/tls_openssl.c b/src/tls_openssl.c new file mode 100644 index 0000000..ad7ec5a --- /dev/null +++ b/src/tls_openssl.c @@ -0,0 +1,223 @@ +/* tls.c +** libstrophe XMPP client library -- TLS abstraction openssl impl. +** +** Copyright (C) 2005 OGG, LCC. All rights reserved. +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This software is distributed under license and may not be copied, +** modified or distributed except as expressly authorized under the +** terms of the license contained in the file LICENSE.txt in this +** distribution. +*/ + +#include +#include + +#include "common.h" +#include "tls.h" +#include "sock.h" + +struct _tls { + xmpp_ctx_t *ctx; + sock_t sock; + SSL_CTX *ssl_ctx; + SSL *ssl; + int lasterror; +}; + +void tls_initialize(void) +{ +#ifdef _WIN32 + RAND_screen (); +#else + { + char filename[512]; + char *file; + + filename[0] = '\0'; + file = RAND_file_name(filename, 512); + + if (file && strlen(file) != 0) { + RAND_load_file(filename, /*It's over */9000); + } + } +#endif + SSL_library_init(); + SSL_load_error_strings(); + return; +} + +void tls_shutdown(void) +{ + return; +} + +void tls_logerror(tls_t *tls) +{ + char *texterror = NULL; + + switch(tls->lasterror) { + case SSL_ERROR_NONE: + texterror = "No error."; + break; + case SSL_ERROR_ZERO_RETURN: + texterror = "Connection closed."; + break; + case SSL_ERROR_WANT_READ: + texterror = "Data waiting to read."; + break; + case SSL_ERROR_WANT_WRITE: + texterror = "Data waiting to write."; + break; + case SSL_ERROR_WANT_CONNECT: + texterror = "Not yet connected."; + break; + case SSL_ERROR_WANT_ACCEPT: + texterror = "Not yet accepted."; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + texterror = "No certification."; + break; + case SSL_ERROR_SYSCALL: + texterror = "I/O error."; + break; + case SSL_ERROR_SSL: + texterror = "SSL library internal error"; + break; + default: + texterror = "Unknown error"; + break; + } + + xmpp_debug(tls->ctx, "xmpp", "SSL error: %s", texterror); +} + +tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock) +{ + tls_t *tls = xmpp_alloc(ctx, sizeof(*tls)); + + if (tls) { + int ret; + memset(tls, 0, sizeof(*tls)); + + tls->ctx = ctx; + tls->sock = sock; + tls->ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + + SSL_CTX_set_client_cert_cb(tls->ssl_ctx, NULL); + SSL_CTX_set_mode (tls->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_verify (tls->ssl_ctx, SSL_VERIFY_NONE, NULL); + + tls->ssl = SSL_new(tls->ssl_ctx); + + ret = SSL_set_fd(tls->ssl, sock); + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_error(tls); + tls_free(tls); + tls = NULL; + } + } + + return tls; +} + +void tls_free(tls_t *tls) +{ + SSL_CTX_free(tls->ssl_ctx); + xmpp_free(tls->ctx, tls); + return; +} + +int tls_set_credentials(tls_t *tls, const char *cafilename) +{ + return -1; +} + +int tls_start(tls_t *tls) +{ + int ret = -1; + + /* Since we're non-blocking, loop the connect call until it + succeeds or fails */ + while (ret == -1) { + ret = SSL_connect(tls->ssl); + + /* wait for something to happen on the sock before looping back */ + if (ret == -1) { + fd_set fds; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 1000; + + FD_ZERO(&fds); + FD_SET(tls->sock, &fds); + + select(tls->sock + 1, &fds, &fds, NULL, &tv); + } + } + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_logerror(tls); + return 0; + } + + return 1; + +} + +int tls_stop(tls_t *tls) +{ + int ret; + + ret = SSL_shutdown(tls->ssl); + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_logerror(tls); + return 0; + } + + return 1; +} + +int tls_error(tls_t *tls) +{ + return tls->lasterror; +} + +int tls_is_recoverable(int error) +{ + return (error == SSL_ERROR_NONE || error == SSL_ERROR_WANT_READ + || error == SSL_ERROR_WANT_WRITE + || error == SSL_ERROR_WANT_CONNECT + || error == SSL_ERROR_WANT_ACCEPT); +} + +int tls_read(tls_t *tls, void * const buff, const size_t len) +{ + int ret = SSL_read(tls->ssl, buff, len); + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_logerror(tls); + } + + return ret; +} + +int tls_write(tls_t *tls, const void * const buff, const size_t len) +{ + int ret = SSL_write(tls->ssl, buff, len); + + if (ret <= 0) { + tls->lasterror = SSL_get_error(tls->ssl, ret); + tls_logerror(tls); + } + + return ret; +}