/*
* jabberd - Jabber Open Source Server
* Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
* Ryan Eatmon, Robert Norris
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
*/
/**
* this plugin implements the traditional SSL "wrappermode" streams and
* STARTTLS extension documented in xmpp-core
*/
#include "sx.h"
/* code stolen from SSL_CTX_set_verify(3) */
static int _sx_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
char buf[256];
X509 *err_cert;
int err, depth;
SSL *ssl;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
/*
* Retrieve the pointer to the SSL of the connection currently treated
* and the application specific data stored into the SSL object.
*/
ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
if (!preverify_ok) {
_sx_debug(ZONE, "verify error:num=%d:%s:depth=%d:%s\n", err,
X509_verify_cert_error_string(err), depth, buf);
}
else
{
_sx_debug(ZONE, "OK! depth=%d:%s", depth, buf);
}
/*
* At this point, err contains the last verification error. We can use
* it for something special
*/
if (!preverify_ok && (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT))
{
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
_sx_debug(ZONE, "issuer= %s\n", buf);
}
return preverify_ok;
}
static void _sx_ssl_starttls_notify_proceed(sx_t s, void *arg) {
_sx_debug(ZONE, "preparing for starttls");
_sx_reset(s);
/* start listening */
sx_server_init(s, s->flags | SX_SSL_WRAPPER);
}
static int _sx_ssl_process(sx_t s, sx_plugin_t p, nad_t nad) {
int flags;
char *ns = NULL, *to = NULL, *from = NULL, *version = NULL;
sx_error_t sxe;
/* not interested if we're a server and we never offered it */
if(s->type == type_SERVER && !(s->flags & SX_SSL_STARTTLS_OFFER))
return 1;
/* only want tls packets */
if(NAD_ENS(nad, 0) < 0 || NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_TLS) || strncmp(NAD_NURI(nad, NAD_ENS(nad, 0)), uri_TLS, strlen(uri_TLS)) != 0)
return 1;
/* starttls from client */
if(s->type == type_SERVER) {
if(NAD_ENAME_L(nad, 0) == 8 && strncmp(NAD_ENAME(nad, 0), "starttls", 8) == 0) {
nad_free(nad);
/* can't go on if we've been here before */
if(s->ssf > 0) {
_sx_debug(ZONE, "starttls requested on already encrypted channel, dropping packet");
return 0;
}
/* can't go on if we're on compressed stream */
if(s->compressed > 0) {
_sx_debug(ZONE, "starttls requested on already compressed channel, dropping packet");
return 0;
}
_sx_debug(ZONE, "starttls requested, setting up");
/* go ahead */
jqueue_push(s->wbufq, _sx_buffer_new("", strlen(uri_TLS) + 19, _sx_ssl_starttls_notify_proceed, NULL), 0);
s->want_write = 1;
/* handled the packet */
return 0;
}
}
else if(s->type == type_CLIENT) {
/* kick off the handshake */
if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "proceed", 7) == 0) {
nad_free(nad);
/* save interesting bits */
flags = s->flags;
if(s->ns != NULL) ns = strdup(s->ns);
if(s->req_to != NULL) to = strdup(s->req_to);
if(s->req_from != NULL) from = strdup(s->req_from);
if(s->req_version != NULL) version = strdup(s->req_version);
/* reset state */
_sx_reset(s);
_sx_debug(ZONE, "server ready for ssl, starting");
/* second time round */
sx_client_init(s, flags | SX_SSL_WRAPPER, ns, to, from, version);
/* free bits */
if(ns != NULL) free(ns);
if(to != NULL) free(to);
if(from != NULL) free(from);
if(version != NULL) free(version);
return 0;
}
/* busted server */
if(NAD_ENAME_L(nad, 0) == 7 && strncmp(NAD_ENAME(nad, 0), "failure", 7) == 0) {
nad_free(nad);
/* free the pemfile arg */
if(s->plugin_data[p->index] != NULL) {
if( ((_sx_ssl_conn_t)s->plugin_data[p->index])->pemfile != NULL )
free(((_sx_ssl_conn_t)s->plugin_data[p->index])->pemfile);
free(s->plugin_data[p->index]);
s->plugin_data[p->index] = NULL;
}
_sx_debug(ZONE, "server can't handle ssl, business as usual");
_sx_gen_error(sxe, SX_ERR_STARTTLS_FAILURE, "STARTTLS failure", "Server was unable to prepare for the TLS handshake");
_sx_event(s, event_ERROR, (void *) &sxe);
return 0;
}
}
_sx_debug(ZONE, "unknown starttls namespace element '%.*s', dropping packet", NAD_ENAME_L(nad, 0), NAD_ENAME(nad, 0));
nad_free(nad);
return 0;
}
static void _sx_ssl_features(sx_t s, sx_plugin_t p, nad_t nad) {
int ns;
/* if the session is already encrypted, or the app told us not to,
* or session is compressed then we don't offer anything */
if(s->state > state_STREAM || s->ssf > 0 || !(s->flags & SX_SSL_STARTTLS_OFFER) || s->compressed)
return;
_sx_debug(ZONE, "offering starttls");
ns = nad_add_namespace(nad, uri_TLS, NULL);
nad_append_elem(nad, ns, "starttls", 1);
if(s->flags & SX_SSL_STARTTLS_REQUIRE)
nad_append_elem(nad, ns, "required", 2);
}
static int _sx_ssl_handshake(sx_t s, _sx_ssl_conn_t sc) {
int ret, err;
char *errstring;
sx_error_t sxe;
/* work on establishing the channel */
while(!SSL_is_init_finished(sc->ssl)) {
_sx_debug(ZONE, "secure channel not established, handshake in progress");
/* we can't handshake if they want to read, but there's nothing to read */
if(sc->last_state == SX_SSL_STATE_WANT_READ && BIO_pending(sc->rbio) == 0)
return 0;
/* more handshake */
if(s->type == type_CLIENT)
ret = SSL_connect(sc->ssl);
else
ret = SSL_accept(sc->ssl);
/* check if we're done */
if(ret == 1) {
_sx_debug(ZONE, "secure channel established");
sc->last_state = SX_SSL_STATE_NONE;
s->ssf = SSL_get_cipher_bits(sc->ssl, NULL);
_sx_debug(ZONE, "using cipher %s (%d bits)", SSL_get_cipher_name(sc->ssl), s->ssf);
return 1;
}
/* error checking */
else if(ret <= 0) {
err = SSL_get_error(sc->ssl, ret);
if(err == SSL_ERROR_WANT_READ)
sc->last_state = SX_SSL_STATE_WANT_READ;
else if(err == SSL_ERROR_WANT_WRITE)
sc->last_state = SX_SSL_STATE_WANT_WRITE;
else {
/* fatal error */
sc->last_state = SX_SSL_STATE_ERROR;
errstring = ERR_error_string(ERR_get_error(), NULL);
_sx_debug(ZONE, "openssl error: %s", errstring);
/* throw an error */
_sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring);
_sx_event(s, event_ERROR, (void *) &sxe);
_sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring);
_sx_close(s);
/* !!! drop queue */
return -1;
}
}
}
return 1;
}
static int _sx_ssl_wio(sx_t s, sx_plugin_t p, sx_buf_t buf) {
_sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index];
int est, ret, err;
sx_buf_t wbuf;
char *errstring;
sx_error_t sxe;
/* sanity */
if(sc->last_state == SX_SSL_STATE_ERROR)
return -2;
_sx_debug(ZONE, "in _sx_ssl_wio");
/* queue the buffer */
if(buf->len > 0) {
_sx_debug(ZONE, "queueing buffer for write");
jqueue_push(sc->wq, _sx_buffer_new(buf->data, buf->len, buf->notify, buf->notify_arg), 0);
_sx_buffer_clear(buf);
buf->notify = NULL;
buf->notify_arg = NULL;
}
/* handshake */
est = _sx_ssl_handshake(s, sc);
if(est < 0)
return -2; /* fatal error */
/* channel established, do some real writing */
wbuf = NULL;
if(est > 0 && jqueue_size(sc->wq) > 0) {
_sx_debug(ZONE, "preparing queued buffer for write");
wbuf = jqueue_pull(sc->wq);
ret = SSL_write(sc->ssl, wbuf->data, wbuf->len);
if(ret <= 0) {
/* something's wrong */
_sx_debug(ZONE, "write failed, requeuing buffer");
/* requeue the buffer */
jqueue_push(sc->wq, wbuf, (sc->wq->front != NULL) ? sc->wq->front->priority + 1 : 0);
/* error checking */
err = SSL_get_error(sc->ssl, ret);
if(err == SSL_ERROR_ZERO_RETURN) {
/* ssl channel closed, we're done */
_sx_close(s);
}
if(err == SSL_ERROR_WANT_READ) {
/* we'll be renegotiating next time */
_sx_debug(ZONE, "renegotiation started");
sc->last_state = SX_SSL_STATE_WANT_READ;
}
else {
sc->last_state = SX_SSL_STATE_ERROR;
/* something very bad */
errstring = ERR_error_string(ERR_get_error(), NULL);
_sx_debug(ZONE, "openssl error: %s", errstring);
/* throw an error */
_sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring);
_sx_event(s, event_ERROR, (void *) &sxe);
_sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring);
_sx_close(s);
/* !!! drop queue */
return -2; /* fatal */
}
}
}
/* prepare the buffer with stuff to write */
if(BIO_pending(sc->wbio) > 0) {
int bytes_pending = BIO_pending(sc->wbio);
assert(buf->len == 0);
_sx_buffer_alloc_margin(buf, 0, bytes_pending);
BIO_read(sc->wbio, buf->data, bytes_pending);
buf->len += bytes_pending;
/* restore notify and clean up */
if(wbuf != NULL) {
buf->notify = wbuf->notify;
buf->notify_arg = wbuf->notify_arg;
_sx_buffer_free(wbuf);
}
_sx_debug(ZONE, "prepared %d ssl bytes for write", buf->len);
}
/* flag if we want to read */
if(sc->last_state == SX_SSL_STATE_WANT_READ || sc->last_state == SX_SSL_STATE_NONE)
s->want_read = 1;
return 1;
}
static int _sx_ssl_rio(sx_t s, sx_plugin_t p, sx_buf_t buf) {
_sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index];
int est, ret, err, pending;
char *errstring;
sx_error_t sxe;
/* sanity */
if(sc->last_state == SX_SSL_STATE_ERROR)
return -1;
_sx_debug(ZONE, "in _sx_ssl_rio");
/* move the data into the ssl read buffer */
if(buf->len > 0) {
_sx_debug(ZONE, "loading %d bytes into ssl read buffer", buf->len);
BIO_write(sc->rbio, buf->data, buf->len);
_sx_buffer_clear(buf);
}
/* handshake */
est = _sx_ssl_handshake(s, sc);
if(est < 0)
return -1; /* fatal error */
/* channel is up, slurp up the read buffer */
if(est > 0) {
pending = SSL_pending(sc->ssl);
if(pending == 0)
pending = BIO_pending(sc->rbio);
/* get it all */
while((pending = SSL_pending(sc->ssl)) > 0 || (pending = BIO_pending(sc->rbio)) > 0) {
_sx_buffer_alloc_margin(buf, 0, pending);
ret = SSL_read(sc->ssl, &(buf->data[buf->len]), pending);
if (ret == 0)
{
/* ret will equal zero if the SSL stream was closed.
(See the SSL_read manpage.) */
/* If the SSL Shutdown happened properly,
(i.e. we got an SSL "close notify")
then proccess the last packet recieved. */
if (SSL_get_shutdown(sc->ssl) == SSL_RECEIVED_SHUTDOWN)
{
_sx_close(s);
break;
}
/* If the SSL stream was just closed and not shutdown,
drop the last packet recieved.
WARNING: This may cause clients that use SSLv2 and
earlier to not log out properly. */
err = SSL_get_error(sc->ssl, ret);
_sx_buffer_clear(buf);
if(err == SSL_ERROR_ZERO_RETURN) {
/* ssl channel closed, we're done */
_sx_close(s);
}
return -1;
}
else if(ret < 0) {
/* ret will be negative if the SSL stream needs
more data, or if there was a SSL error.
(See the SSL_read manpage.) */
err = SSL_get_error(sc->ssl, ret);
/* ssl block incomplete, need more */
if(err == SSL_ERROR_WANT_READ) {
sc->last_state = SX_SSL_STATE_WANT_READ;
break;
}
/* something's wrong */
_sx_buffer_clear(buf);
/* !!! need checks for renegotiation */
sc->last_state = SX_SSL_STATE_ERROR;
errstring = ERR_error_string(ERR_get_error(), NULL);
_sx_debug(ZONE, "openssl error: %s", errstring);
/* throw an error */
_sx_gen_error(sxe, SX_ERR_SSL, "SSL handshake error", errstring);
_sx_event(s, event_ERROR, (void *) &sxe);
_sx_error(s, stream_err_INTERNAL_SERVER_ERROR, errstring);
_sx_close(s);
/* !!! drop queue */
return -1;
}
buf->len += ret;
}
}
/* flag if stuff to write */
if(BIO_pending(sc->wbio) > 0 || (est > 0 && jqueue_size(sc->wq) > 0))
s->want_write = 1;
/* flag if we want to read */
if(sc->last_state == SX_SSL_STATE_WANT_READ || sc->last_state == SX_SSL_STATE_NONE)
s->want_read = 1;
if(buf->len == 0)
return 0;
return 1;
}
static void _sx_ssl_client(sx_t s, sx_plugin_t p) {
_sx_ssl_conn_t sc;
char *pemfile = NULL;
int ret;
/* only bothering if they asked for wrappermode */
if(!(s->flags & SX_SSL_WRAPPER) || s->ssf > 0)
return;
_sx_debug(ZONE, "preparing for ssl connect for %d", s->tag);
sc = (_sx_ssl_conn_t) malloc(sizeof(struct _sx_ssl_conn_st));
memset(sc, 0, sizeof(struct _sx_ssl_conn_st));
/* create the buffers */
sc->rbio = BIO_new(BIO_s_mem());
sc->wbio = BIO_new(BIO_s_mem());
/* new ssl conn */
sc->ssl = SSL_new((SSL_CTX *) p->private);
SSL_set_bio(sc->ssl, sc->rbio, sc->wbio);
SSL_set_connect_state(sc->ssl);
/* empty external_id */
sc->external_id = NULL;
/* alternate pemfile */
/* !!! figure out how to error correctly here - just returning will cause
* us to send a normal unencrypted stream start while the server is
* waiting for ClientHelo. the server will flag an error, but it won't
* help the admin at all to figure out what happened */
if(s->plugin_data[p->index] != NULL) {
pemfile = ((_sx_ssl_conn_t)s->plugin_data[p->index])->pemfile;
free(s->plugin_data[p->index]);
s->plugin_data[p->index] = NULL;
}
if(pemfile != NULL) {
/* load the certificate */
ret = SSL_use_certificate_file(sc->ssl, pemfile, SSL_FILETYPE_PEM);
if(ret != 1) {
_sx_debug(ZONE, "couldn't load alternate certificate from %s", pemfile);
SSL_free(sc->ssl);
free(sc);
free(pemfile);
return;
}
/* load the private key */
ret = SSL_use_PrivateKey_file(sc->ssl, pemfile, SSL_FILETYPE_PEM);
if(ret != 1) {
_sx_debug(ZONE, "couldn't load alternate private key from %s", pemfile);
SSL_free(sc->ssl);
free(sc);
free(pemfile);
return;
}
/* check the private key matches the certificate */
ret = SSL_check_private_key(sc->ssl);
if(ret != 1) {
_sx_debug(ZONE, "private key does not match certificate public key");
SSL_free(sc->ssl);
free(sc);
free(pemfile);
return;
}
_sx_debug(ZONE, "loaded alternate pemfile %s", pemfile);
free(pemfile);
}
/* buffer queue */
sc->wq = jqueue_new();
s->plugin_data[p->index] = (void *) sc;
/* bring the plugin online */
_sx_chain_io_plugin(s, p);
}
static void _sx_ssl_server(sx_t s, sx_plugin_t p) {
_sx_ssl_conn_t sc;
/* only bothering if they asked for wrappermode */
if(!(s->flags & SX_SSL_WRAPPER) || s->ssf > 0)
return;
_sx_debug(ZONE, "preparing for ssl accept for %d", s->tag);
sc = (_sx_ssl_conn_t) malloc(sizeof(struct _sx_ssl_conn_st));
memset(sc, 0, sizeof(struct _sx_ssl_conn_st));
/* create the buffers */
sc->rbio = BIO_new(BIO_s_mem());
sc->wbio = BIO_new(BIO_s_mem());
/* new ssl conn */
sc->ssl = SSL_new((SSL_CTX *) p->private);
SSL_set_bio(sc->ssl, sc->rbio, sc->wbio);
SSL_set_accept_state(sc->ssl);
/* empty external_id */
sc->external_id = NULL;
/* buffer queue */
sc->wq = jqueue_new();
s->plugin_data[p->index] = (void *) sc;
/* bring the plugin online */
_sx_chain_io_plugin(s, p);
}
/** cleanup */
static void _sx_ssl_free(sx_t s, sx_plugin_t p) {
_sx_ssl_conn_t sc = (_sx_ssl_conn_t) s->plugin_data[p->index];
sx_buf_t buf;
if(sc == NULL)
return;
log_debug(ZONE, "cleaning up conn state");
if(s->type == type_NONE) {
free(sc);
return;
}
if(sc->external_id != NULL) free(sc->external_id);
if(sc->pemfile != NULL) free(sc->pemfile);
if(sc->ssl != NULL) SSL_free(sc->ssl); /* frees wbio and rbio too */
if(sc->wq != NULL) {
while((buf = jqueue_pull(sc->wq)) != NULL)
_sx_buffer_free(buf);
jqueue_free(sc->wq);
}
free(sc);
s->plugin_data[p->index] = NULL;
}
static void _sx_ssl_unload(sx_plugin_t p) {
SSL_CTX_free((SSL_CTX *) p->private);
}
int sx_openssl_initialized = 0;
/** args: pemfile, cachain, mode */
int sx_ssl_init(sx_env_t env, sx_plugin_t p, va_list args) {
char *pemfile, *cachain;
SSL_CTX *ctx;
int ret;
int mode;
_sx_debug(ZONE, "initialising ssl plugin");
pemfile = va_arg(args, char *);
if(pemfile == NULL)
return 1;
if(p->private != NULL)
return 1;
cachain = va_arg(args, char *);
mode = va_arg(args, int);
/* !!! output openssl error messages to the debug log */
/* openssl startup */
SSL_library_init();
SSL_load_error_strings();
sx_openssl_initialized = 1;
/* create the context */
ctx = SSL_CTX_new(SSLv23_method());
if(ctx == NULL) {
_sx_debug(ZONE, "ssl context creation failed");
return 1;
}
/* Load the CA chain, if configured */
if (cachain != NULL) {
ret = SSL_CTX_load_verify_locations (ctx, cachain, NULL);
if(ret != 1) {
_sx_debug(ZONE, "WARNING: couldn't load CA chain: %s", cachain);
}
}
/* load the certificate */
ret = SSL_CTX_use_certificate_chain_file(ctx, pemfile);
if(ret != 1) {
_sx_debug(ZONE, "couldn't load certificate from %s", pemfile);
SSL_CTX_free(ctx);
return 1;
}
/* load the private key */
ret = SSL_CTX_use_PrivateKey_file(ctx, pemfile, SSL_FILETYPE_PEM);
if(ret != 1) {
_sx_debug(ZONE, "couldn't load private key from %s", pemfile);
SSL_CTX_free(ctx);
return 1;
}
/* check the private key matches the certificate */
ret = SSL_CTX_check_private_key(ctx);
if(ret != 1) {
_sx_debug(ZONE, "private key does not match certificate public key");
SSL_CTX_free(ctx);
return 1;
}
_sx_debug(ZONE, "Setting verify mode to %02x", mode);
SSL_CTX_set_verify(ctx, mode, _sx_ssl_verify_callback);
/* its good */
_sx_debug(ZONE, "ssl context initialised; certificate and key loaded from %s", pemfile);
p->magic = SX_SSL_MAGIC;
p->private = (void *) ctx;
p->unload = _sx_ssl_unload;
p->client = _sx_ssl_client;
p->server = _sx_ssl_server;
p->rio = _sx_ssl_rio;
p->wio = _sx_ssl_wio;
p->features = _sx_ssl_features;
p->process = _sx_ssl_process;
p->free = _sx_ssl_free;
return 0;
}
int sx_ssl_client_starttls(sx_plugin_t p, sx_t s, char *pemfile) {
assert((int) (p != NULL));
assert((int) (s != NULL));
/* sanity */
if(s->type != type_CLIENT || s->state != state_STREAM) {
_sx_debug(ZONE, "wrong conn type or state for client starttls");
return 1;
}
/* check if we're already encrypted or compressed */
if(s->ssf > 0 || s->compressed) {
_sx_debug(ZONE, "encrypted channel already established");
return 1;
}
_sx_debug(ZONE, "initiating starttls sequence");
/* save the given pemfile for later */
if(pemfile != NULL) {
s->plugin_data[p->index] = (_sx_ssl_conn_t) malloc(sizeof(struct _sx_ssl_conn_st));
memset(s->plugin_data[p->index], 0, sizeof(struct _sx_ssl_conn_st));
((_sx_ssl_conn_t)s->plugin_data[p->index])->pemfile = strdup(pemfile);
}
/* go */
jqueue_push(s->wbufq, _sx_buffer_new("", strlen(uri_TLS) + 20, NULL, NULL), 0);
s->want_write = 1;
_sx_event(s, event_WANT_WRITE, NULL);
return 0;
}