/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/* conn.c - implement Connection type
*
* This file implements the interface defined in conn.h.
*
* Richard Braakman
*
* SSL client implementation contributed by
* Jarkko Kovala
*
* SSL server implementation contributed by
* Stipe Tolj for Wapme Systems AG
*/
/* TODO: unlocked_close() on error */
/* TODO: have I/O functions check if connection is open */
/* TODO: have conn_open_tcp do a non-blocking connect() */
#include
#include
#include
#include
#include
#include
#include "gwlib/gwlib.h"
#ifdef HAVE_LIBSSL
#include
#include
static SSL_CTX *global_ssl_context = NULL;
static SSL_CTX *global_server_ssl_context = NULL;
#endif /* HAVE_LIBSSL */
typedef unsigned long (*CRYPTO_CALLBACK_PTR)(void);
/*
* This used to be 4096. It is now 0 so that callers don't have to
* deal with the complexities of buffering (i.e. deciding when to
* flush) unless they want to.
* FIXME: Figure out how to combine buffering sensibly with use of
* conn_register.
*/
#define DEFAULT_OUTPUT_BUFFERING 0
#define SSL_CONN_TIMEOUT 30
struct Connection
{
/* We use separate locks for input and ouput fields, so that
* read and write activities don't have to get in each other's
* way. If you need both, then acquire the outlock first. */
Mutex *inlock;
Mutex *outlock;
volatile sig_atomic_t claimed;
#ifndef NO_GWASSERT
long claiming_thread;
#endif
/* fd value is read-only and is not locked */
int fd;
/* socket state */
enum {yes,no} connected;
/* Protected by outlock */
Octstr *outbuf;
long outbufpos; /* start of unwritten data in outbuf */
/* Try to buffer writes until there are this many octets to send.
* Set it to 0 to get an unbuffered connection. */
unsigned int output_buffering;
/* Protected by inlock */
Octstr *inbuf;
long inbufpos; /* start of unread data in inbuf */
int read_eof; /* we encountered eof on read */
int io_error; /* we encountered error on IO operation */
/* Protected by both locks when updating, so you need only one
* of the locks when reading. */
FDSet *registered;
conn_callback_t *callback;
void *callback_data;
conn_callback_data_destroyer_t *callback_data_destroyer;
/* Protected by inlock */
int listening_pollin;
/* Protected by outlock */
int listening_pollout;
#ifdef HAVE_LIBSSL
SSL *ssl;
X509 *peer_certificate;
#endif /* HAVE_LIBSSL */
};
static void unlocked_register_pollin(Connection *conn, int onoff);
static void unlocked_register_pollout(Connection *conn, int onoff);
/* There are a number of functions that play with POLLIN and POLLOUT flags.
* The general rule is that we always want to poll for POLLIN except when
* we have detected eof (which may be reported as eternal POLLIN), and
* we want to poll for POLLOUT only if there's data waiting in the
* output buffer. If output buffering is set, we may not want to poll for
* POLLOUT if there's not enough data waiting, which is why we have
* unlocked_try_write. */
/* Macros to get more information for debugging purposes */
#define unlock_in(conn) unlock_in_real(conn, __FILE__, __LINE__, __func__)
#define unlock_out(conn) unlock_out_real(conn, __FILE__, __LINE__, __func__)
/* Lock a Connection's read direction, if the Connection is unclaimed */
static void inline lock_in(Connection *conn)
{
gw_assert(conn != NULL);
if (conn->claimed)
gw_assert(gwthread_self() == conn->claiming_thread);
else
mutex_lock(conn->inlock);
}
/* Unlock a Connection's read direction, if the Connection is unclaimed */
static void inline unlock_in_real(Connection *conn, char *file, int line, const char *func)
{
int ret;
gw_assert(conn != NULL);
if (!conn->claimed && (ret = mutex_unlock(conn->inlock)) != 0) {
panic(0, "%s:%ld: %s: Mutex unlock failed. "
"(Called from %s:%ld:%s.)",
__FILE__, (long) __LINE__, __func__,
file, (long) line, func);
}
}
/* Lock a Connection's write direction, if the Connection is unclaimed */
static void inline lock_out(Connection *conn)
{
gw_assert(conn != NULL);
if (conn->claimed)
gw_assert(gwthread_self() == conn->claiming_thread);
else
mutex_lock(conn->outlock);
}
/* Unlock a Connection's write direction, if the Connection is unclaimed */
static void inline unlock_out_real(Connection *conn, char *file, int line, const char *func)
{
int ret;
gw_assert(conn != NULL);
if (!conn->claimed && (ret = mutex_unlock(conn->outlock)) != 0) {
panic(0, "%s:%ld: %s: Mutex unlock failed. "
"(Called from %s:%ld:%s.)",
__FILE__, (long) __LINE__, __func__,
file, (long) line, func);
}
}
/* Return the number of bytes in the Connection's output buffer */
static long inline unlocked_outbuf_len(Connection *conn)
{
return octstr_len(conn->outbuf) - conn->outbufpos;
}
/* Return the number of bytes in the Connection's input buffer */
static long inline unlocked_inbuf_len(Connection *conn)
{
return octstr_len(conn->inbuf) - conn->inbufpos;
}
/* Send as much data as can be sent without blocking. Return the number
* of bytes written, or -1 in case of error. */
static long unlocked_write(Connection *conn)
{
long ret = 0;
#ifdef HAVE_LIBSSL
if (conn->ssl != NULL) {
if (octstr_len(conn->outbuf) - conn->outbufpos > 0)
ret = SSL_write(conn->ssl,
octstr_get_cstr(conn->outbuf) + conn->outbufpos,
octstr_len(conn->outbuf) - conn->outbufpos);
if (ret < 0) {
int SSL_error = SSL_get_error(conn->ssl, ret);
if (SSL_error == SSL_ERROR_WANT_READ || SSL_error == SSL_ERROR_WANT_WRITE) {
ret = 0; /* no error */
} else {
error(errno, "SSL write failed: OpenSSL error %d: %s",
SSL_error, ERR_error_string(SSL_error, NULL));
return -1;
}
}
} else
#endif /* HAVE_LIBSSL */
ret = octstr_write_data(conn->outbuf, conn->fd, conn->outbufpos);
if (ret < 0) {
conn->io_error = 1;
return -1;
}
conn->outbufpos += ret;
/* Heuristic: Discard the already-written data if it's more than
* half of the total. This should keep the buffer size small
* without wasting too many cycles on moving data around. */
if (conn->outbufpos > octstr_len(conn->outbuf) / 2) {
octstr_delete(conn->outbuf, 0, conn->outbufpos);
conn->outbufpos = 0;
}
if (conn->registered)
unlocked_register_pollout(conn, unlocked_outbuf_len(conn) > 0);
return ret;
}
/* Try to empty the output buffer without blocking. Return 0 for success,
* 1 if there is still data left in the buffer, and -1 for errors. */
static int unlocked_try_write(Connection *conn)
{
long len;
len = unlocked_outbuf_len(conn);
if (len == 0)
return 0;
if (len < (long) conn->output_buffering)
return 1;
if (unlocked_write(conn) < 0)
return -1;
if (unlocked_outbuf_len(conn) > 0)
return 1;
return 0;
}
/* Read whatever data is currently available, up to an internal maximum. */
static void unlocked_read(Connection *conn)
{
unsigned char buf[4096];
long len;
if (conn->inbufpos > 0) {
octstr_delete(conn->inbuf, 0, conn->inbufpos);
conn->inbufpos = 0;
}
#ifdef HAVE_LIBSSL
if (conn->ssl != NULL) {
len = SSL_read(conn->ssl, buf, sizeof(buf));
} else
#endif /* HAVE_LIBSSL */
len = read(conn->fd, buf, sizeof(buf));
if (len < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
return;
#ifdef HAVE_LIBSSL
if (conn->ssl) {
int SSL_error = SSL_get_error(conn->ssl, len);
if (SSL_error == SSL_ERROR_WANT_WRITE || SSL_error == SSL_ERROR_WANT_READ)
return; /* no error */
error(errno, "SSL read failed: OpenSSL error %d: %s",
SSL_error, ERR_error_string(SSL_error, NULL));
}
else
#endif /* HAVE_LIBSSL */
error(errno, "Error reading from fd %d:", conn->fd);
conn->io_error = 1;
if (conn->registered)
unlocked_register_pollin(conn, 0);
return;
} else if (len == 0) {
conn->read_eof = 1;
if (conn->registered)
unlocked_register_pollin(conn, 0);
} else {
octstr_append_data(conn->inbuf, buf, len);
}
}
/* Cut "length" octets from the input buffer and return them as an Octstr */
static Octstr *unlocked_get(Connection *conn, long length)
{
Octstr *result = NULL;
gw_assert(unlocked_inbuf_len(conn) >= length);
result = octstr_copy(conn->inbuf, conn->inbufpos, length);
conn->inbufpos += length;
return result;
}
/* Tell the fdset whether we are interested in POLLIN events, but only
* if the status changed. (Calling fdset_listen can be expensive if
* it requires synchronization with the polling thread.)
* We must already have the inlock.
*/
static void unlocked_register_pollin(Connection *conn, int onoff)
{
gw_assert(conn->registered);
if (onoff == 1 && !conn->listening_pollin) {
/* Turn it on */
conn->listening_pollin = 1;
fdset_listen(conn->registered, conn->fd, POLLIN, POLLIN);
} else if (onoff == 0 && conn->listening_pollin) {
/* Turn it off */
conn->listening_pollin = 0;
fdset_listen(conn->registered, conn->fd, POLLIN, 0);
}
}
/* Tell the fdset whether we are interested in POLLOUT events, but only
* if the status changed. (Calling fdset_listen can be expensive if
* it requires synchronization with the polling thread.)
* We must already have the outlock.
*/
static void unlocked_register_pollout(Connection *conn, int onoff)
{
gw_assert(conn->registered);
if (onoff == 1 && !conn->listening_pollout) {
/* Turn it on */
conn->listening_pollout = 1;
fdset_listen(conn->registered, conn->fd, POLLOUT, POLLOUT);
} else if (onoff == 0 && conn->listening_pollout) {
/* Turn it off */
conn->listening_pollout = 0;
fdset_listen(conn->registered, conn->fd, POLLOUT, 0);
}
}
#ifdef HAVE_LIBSSL
Connection *conn_open_ssl(Octstr *host, int port, Octstr *certkeyfile,
Octstr *our_host)
{
Connection *ret;
/* open the TCP connection */
if (!(ret = conn_open_tcp(host, port, our_host))) {
return NULL;
}
ret->ssl = SSL_new(global_ssl_context);
/*
* The current thread's error queue must be empty before
* the TLS/SSL I/O operation is attempted, or SSL_get_error()
* will not work reliably.
*/
ERR_clear_error();
if (certkeyfile != NULL) {
SSL_use_certificate_file(ret->ssl, octstr_get_cstr(certkeyfile),
SSL_FILETYPE_PEM);
SSL_use_PrivateKey_file(ret->ssl, octstr_get_cstr(certkeyfile),
SSL_FILETYPE_PEM);
if (SSL_check_private_key(ret->ssl) != 1) {
error(0, "conn_open_ssl: private key isn't consistent with the "
"certificate from file %s (or failed reading the file)",
octstr_get_cstr(certkeyfile));
goto error;
}
}
/* SSL_set_fd can fail, so check it */
if (SSL_set_fd(ret->ssl, ret->fd) == 0) {
/* SSL_set_fd failed, log error */
error(errno, "SSL: OpenSSL: %.256s", ERR_error_string(ERR_get_error(), NULL));
goto error;
}
/*
* make sure the socket is non-blocking while we do SSL_connect
*/
if (socket_set_blocking(ret->fd, 0) < 0) {
goto error;
}
BIO_set_nbio(SSL_get_rbio(ret->ssl), 1);
BIO_set_nbio(SSL_get_wbio(ret->ssl), 1);
SSL_set_connect_state(ret->ssl);
return ret;
error:
conn_destroy(ret);
return NULL;
}
#endif /* HAVE_LIBSSL */
Connection *conn_open_tcp(Octstr *host, int port, Octstr *our_host)
{
return conn_open_tcp_with_port(host, port, 0, our_host);
}
Connection *conn_open_tcp_nb(Octstr *host, int port, Octstr *our_host)
{
return conn_open_tcp_nb_with_port(host, port, 0, our_host);
}
Connection *conn_open_tcp_nb_with_port(Octstr *host, int port, int our_port,
Octstr *our_host)
{
int sockfd;
int done = -1;
Connection *c;
sockfd = tcpip_connect_nb_to_server_with_port(octstr_get_cstr(host), port,
our_port, our_host == NULL ?
NULL : octstr_get_cstr(our_host), &done);
if (sockfd < 0)
return NULL;
c = conn_wrap_fd(sockfd, 0);
if (done != 0) {
c->connected = no;
}
return c;
}
int conn_is_connected(Connection *conn)
{
return conn->connected == yes ? 0 : -1;
}
int conn_get_connect_result(Connection *conn)
{
int err;
socklen_t len;
len = sizeof(err);
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
return -1;
}
if (err) {
return -1;
}
conn->connected = yes;
return 0;
}
Connection *conn_open_tcp_with_port(Octstr *host, int port, int our_port,
Octstr *our_host)
{
int sockfd;
sockfd = tcpip_connect_to_server_with_port(octstr_get_cstr(host), port,
our_port, our_host == NULL ?
NULL : octstr_get_cstr(our_host));
if (sockfd < 0)
return NULL;
return conn_wrap_fd(sockfd, 0);
}
Connection *conn_wrap_fd(int fd, int ssl)
{
Connection *conn;
if (socket_set_blocking(fd, 0) < 0)
return NULL;
conn = gw_malloc(sizeof(*conn));
conn->inlock = mutex_create();
conn->outlock = mutex_create();
conn->claimed = 0;
conn->outbuf = octstr_create("");
conn->outbufpos = 0;
conn->inbuf = octstr_create("");
conn->inbufpos = 0;
conn->fd = fd;
conn->connected = yes;
conn->read_eof = 0;
conn->io_error = 0;
conn->output_buffering = DEFAULT_OUTPUT_BUFFERING;
conn->registered = NULL;
conn->callback = NULL;
conn->callback_data = NULL;
conn->callback_data_destroyer = NULL;
conn->listening_pollin = 0;
conn->listening_pollout = 0;
#ifdef HAVE_LIBSSL
/*
* do all the SSL magic for this connection
*/
if (ssl) {
conn->ssl = SSL_new(global_server_ssl_context);
conn->peer_certificate = NULL;
/* SSL_set_fd can fail, so check it */
if (SSL_set_fd(conn->ssl, conn->fd) == 0) {
/* SSL_set_fd failed, log error and return NULL */
error(errno, "SSL: OpenSSL: %.256s", ERR_error_string(ERR_get_error(), NULL));
conn_destroy(conn);
return NULL;
}
/* SSL_set_verify(conn->ssl, 0, NULL); */
/* set read/write BIO layer to non-blocking mode */
BIO_set_nbio(SSL_get_rbio(conn->ssl), 1);
BIO_set_nbio(SSL_get_wbio(conn->ssl), 1);
/* set accept state , SSL-Handshake will be handled transparent while SSL_[read|write] */
SSL_set_accept_state(conn->ssl);
} else {
conn->ssl = NULL;
conn->peer_certificate = NULL;
}
#endif /* HAVE_LIBSSL */
return conn;
}
void conn_destroy(Connection *conn)
{
int ret;
if (conn == NULL)
return;
/* No locking done here. conn_destroy should not be called
* if any thread might still be interested in the connection. */
if (conn->registered) {
fdset_unregister(conn->registered, conn->fd);
/* call data destroyer if any */
if (conn->callback_data != NULL && conn->callback_data_destroyer != NULL)
conn->callback_data_destroyer(conn->callback_data);
}
if (conn->fd >= 0) {
/* Try to flush any remaining data */
unlocked_try_write(conn);
#ifdef HAVE_LIBSSL
if (conn->ssl != NULL) {
SSL_smart_shutdown(conn->ssl);
SSL_free(conn->ssl);
if (conn->peer_certificate != NULL)
X509_free(conn->peer_certificate);
}
#endif /* HAVE_LIBSSL */
ret = close(conn->fd);
if (ret < 0)
error(errno, "conn_destroy: error on close");
conn->fd = -1;
}
octstr_destroy(conn->outbuf);
octstr_destroy(conn->inbuf);
mutex_destroy(conn->inlock);
mutex_destroy(conn->outlock);
gw_free(conn);
}
void conn_claim(Connection *conn)
{
gw_assert(conn != NULL);
if (conn->claimed)
panic(0, "Connection is being claimed twice!");
conn->claimed = 1;
#ifndef NO_GWASSERT
conn->claiming_thread = gwthread_self();
#endif
}
long conn_outbuf_len(Connection *conn)
{
long len;
lock_out(conn);
len = unlocked_outbuf_len(conn);
unlock_out(conn);
return len;
}
long conn_inbuf_len(Connection *conn)
{
long len;
lock_in(conn);
len = unlocked_inbuf_len(conn);
unlock_in(conn);
return len;
}
int conn_eof(Connection *conn)
{
int eof;
lock_in(conn);
eof = conn->read_eof;
unlock_in(conn);
return eof;
}
int conn_error(Connection *conn)
{
int err;
lock_out(conn);
lock_in(conn);
err = conn->io_error;
unlock_in(conn);
unlock_out(conn);
return err;
}
void conn_set_output_buffering(Connection *conn, unsigned int size)
{
lock_out(conn);
conn->output_buffering = size;
/* If the buffer size is smaller, we may have to write immediately. */
unlocked_try_write(conn);
unlock_out(conn);
}
static void poll_callback(int fd, int revents, void *data)
{
Connection *conn;
int do_callback = 0;
conn = data;
if (conn == NULL) {
error(0, "poll_callback called with NULL connection.");
return;
}
if (conn->fd != fd) {
error(0, "poll_callback called on wrong connection.");
return;
}
/* Get result of nonblocking connect, before any reads and writes
* we must check result (it must be handled in initial callback) */
if (conn->connected == no) {
if (conn->callback)
conn->callback(conn, conn->callback_data);
return;
}
/* If got POLLERR or POLHUP, then unregister the descriptor from the
* fdset and set the error condition variable to let the upper layer
* close and destroy the connection. */
if (revents & (POLLERR|POLLHUP)) {
lock_out(conn);
lock_in(conn);
if (conn->listening_pollin)
unlocked_register_pollin(conn, 0);
if (conn->listening_pollout)
unlocked_register_pollout(conn, 0);
conn->io_error = 1;
unlock_in(conn);
unlock_out(conn);
do_callback = 1;
}
/* If unlocked_write manages to write all pending data, it will
* tell the fdset to stop listening for POLLOUT. */
if (revents & POLLOUT) {
lock_out(conn);
unlocked_write(conn);
if (unlocked_outbuf_len(conn) == 0)
do_callback = 1;
unlock_out(conn);
}
/* We read only in unlocked_read in we received POLLIN, cause the
* descriptor is already broken and of no use anymore. */
if (revents & POLLIN) {
lock_in(conn);
unlocked_read(conn);
unlock_in(conn);
do_callback = 1;
}
if (do_callback && conn->callback)
conn->callback(conn, conn->callback_data);
}
int conn_register_real(Connection *conn, FDSet *fdset,
conn_callback_t callback, void *data, conn_callback_data_destroyer_t *data_destroyer)
{
int events;
int result = 0;
gw_assert(conn != NULL);
if (conn->fd < 0)
return -1;
/* We need both locks if we want to update the registration
* information. */
lock_out(conn);
lock_in(conn);
if (conn->registered == fdset) {
/* Re-registering. Change only the callback info. */
conn->callback = callback;
/* call data destroyer if new data supplied */
if (conn->callback_data != NULL && conn->callback_data != data && conn->callback_data_destroyer != NULL)
conn->callback_data_destroyer(conn->callback_data);
conn->callback_data = data;
conn->callback_data_destroyer = data_destroyer;
result = 0;
} else if (conn->registered) {
/* Already registered to a different fdset. */
result = -1;
} else {
events = 0;
/* For nonconnected socket we must lesten both directions */
if (conn->connected == yes) {
if (conn->read_eof == 0 && conn->io_error == 0)
events |= POLLIN;
if (unlocked_outbuf_len(conn) > 0)
events |= POLLOUT;
} else {
events |= POLLIN | POLLOUT;
}
conn->registered = fdset;
conn->callback = callback;
conn->callback_data = data;
conn->callback_data_destroyer = data_destroyer;
conn->listening_pollin = (events & POLLIN) != 0;
conn->listening_pollout = (events & POLLOUT) != 0;
fdset_register(fdset, conn->fd, events, poll_callback, conn);
result = 0;
}
unlock_in(conn);
unlock_out(conn);
return result;
}
void conn_unregister(Connection *conn)
{
FDSet *set = NULL;
int fd;
gw_assert(conn != NULL);
if (conn == NULL || conn->fd < 0)
return;
/* We need both locks to update the registration information */
lock_out(conn);
lock_in(conn);
if (conn->registered) {
set = conn->registered;
fd = conn->fd;
conn->registered = NULL;
conn->callback = NULL;
/* call data destroyer */
if (conn->callback_data != NULL && conn->callback_data_destroyer != NULL)
conn->callback_data_destroyer(conn->callback_data);
conn->callback_data = NULL;
conn->callback_data_destroyer = NULL;
conn->listening_pollin = 0;
conn->listening_pollout = 0;
}
unlock_in(conn);
unlock_out(conn);
/* now unregister from FDSet */
if (set != NULL)
fdset_unregister(set, fd);
}
int conn_wait(Connection *conn, double seconds)
{
int events;
int ret;
int fd;
lock_out(conn);
/* Try to write any data that might still be waiting to be sent */
ret = unlocked_write(conn);
if (ret < 0) {
unlock_out(conn);
return -1;
}
if (ret > 0) {
/* We did something useful. No need to poll or wait now. */
unlock_out(conn);
return 0;
}
fd = conn->fd;
/* Normally, we block until there is more data available. But
* if any data still needs to be sent, we block until we can
* send it (or there is more data available). We always block
* for reading, unless we know there is no more data coming.
* (Because in that case, poll will keep reporting POLLIN to
* signal the end of the file). If the caller explicitly wants
* to wait even though there is no data to write and we're at
* end of file, then poll for new data anyway because the caller
* apparently doesn't trust eof. */
events = 0;
if (unlocked_outbuf_len(conn) > 0)
events |= POLLOUT;
/* Don't keep the connection locked while we wait */
unlock_out(conn);
/* We need the in lock to query read_eof */
lock_in(conn);
if ((conn->read_eof == 0 && conn->io_error == 0) || events == 0)
events |= POLLIN;
unlock_in(conn);
ret = gwthread_pollfd(fd, events, seconds);
if (ret < 0) {
if (errno == EINTR)
return 0;
error(0, "conn_wait: poll failed on fd %d:", fd);
return -1;
}
if (ret == 0)
return 1;
if (ret & POLLNVAL) {
error(0, "conn_wait: fd %d not open.", fd);
return -1;
}
if (ret & (POLLERR | POLLHUP)) {
/* Call unlocked_read to report the specific error,
* and handle the results of the error. We can't be
* certain that the error still exists, because we
* released the lock for a while. */
lock_in(conn);
unlocked_read(conn);
unlock_in(conn);
return -1;
}
/* If POLLOUT is on, then we must have wanted
* to write something. */
if (ret & POLLOUT) {
lock_out(conn);
unlocked_write(conn);
unlock_out(conn);
}
/* Since we normally select for reading, we must
* try to read here. Otherwise, if the caller loops
* around conn_wait without making conn_read* calls
* in between, we will keep polling this same data. */
if (ret & POLLIN) {
lock_in(conn);
unlocked_read(conn);
unlock_in(conn);
}
return 0;
}
int conn_flush(Connection *conn)
{
int ret;
int revents;
int fd;
lock_out(conn);
ret = unlocked_write(conn);
if (ret < 0) {
unlock_out(conn);
return -1;
}
while (unlocked_outbuf_len(conn) != 0) {
fd = conn->fd;
unlock_out(conn);
revents = gwthread_pollfd(fd, POLLOUT, -1.0);
/* Note: Make sure we have the "out" lock when
* going through the loop again, because the
* loop condition needs it. */
if (revents < 0) {
if (errno == EINTR)
return 1;
error(0, "conn_flush: poll failed on fd %d:", fd);
return -1;
}
if (revents == 0) {
/* We were woken up */
return 1;
}
if (revents & POLLNVAL) {
error(0, "conn_flush: fd %d not open.", fd);
return -1;
}
lock_out(conn);
if (revents & (POLLOUT | POLLERR | POLLHUP)) {
ret = unlocked_write(conn);
if (ret < 0) {
unlock_out(conn);
return -1;
}
}
}
unlock_out(conn);
return 0;
}
int conn_write(Connection *conn, Octstr *data)
{
int ret;
lock_out(conn);
octstr_append(conn->outbuf, data);
ret = unlocked_try_write(conn);
unlock_out(conn);
return ret;
}
int conn_write_data(Connection *conn, unsigned char *data, long length)
{
int ret;
lock_out(conn);
octstr_append_data(conn->outbuf, data, length);
ret = unlocked_try_write(conn);
unlock_out(conn);
return ret;
}
int conn_write_withlen(Connection *conn, Octstr *data)
{
int ret;
unsigned char lengthbuf[4];
encode_network_long(lengthbuf, octstr_len(data));
lock_out(conn);
octstr_append_data(conn->outbuf, lengthbuf, 4);
octstr_append(conn->outbuf, data);
ret = unlocked_try_write(conn);
unlock_out(conn);
return ret;
}
Octstr *conn_read_everything(Connection *conn)
{
Octstr *result = NULL;
lock_in(conn);
if (unlocked_inbuf_len(conn) == 0) {
unlocked_read(conn);
if (unlocked_inbuf_len(conn) == 0) {
unlock_in(conn);
return NULL;
}
}
result = unlocked_get(conn, unlocked_inbuf_len(conn));
gw_claim_area(result);
unlock_in(conn);
return result;
}
Octstr *conn_read_fixed(Connection *conn, long length)
{
Octstr *result = NULL;
if (length < 1)
return NULL;
/* See if the data is already available. If not, try a read(),
* then see if we have enough data after that. If not, give up. */
lock_in(conn);
if (unlocked_inbuf_len(conn) < length) {
unlocked_read(conn);
if (unlocked_inbuf_len(conn) < length) {
unlock_in(conn);
return NULL;
}
}
result = unlocked_get(conn, length);
gw_claim_area(result);
unlock_in(conn);
return result;
}
Octstr *conn_read_line(Connection *conn)
{
Octstr *result = NULL;
long pos;
lock_in(conn);
/* 10 is the code for linefeed. We don't rely on \n because that
* might be a different value on some (strange) systems, and
* we are reading from a network connection. */
pos = octstr_search_char(conn->inbuf, 10, conn->inbufpos);
if (pos < 0) {
unlocked_read(conn);
pos = octstr_search_char(conn->inbuf, 10, conn->inbufpos);
if (pos < 0) {
unlock_in(conn);
return NULL;
}
}
result = unlocked_get(conn, pos - conn->inbufpos);
gw_claim_area(result);
/* Skip the LF, which we left in the buffer */
conn->inbufpos++;
/* If the line was terminated with CR LF, we have to remove
* the CR from the result. */
if (octstr_len(result) > 0 &&
octstr_get_char(result, octstr_len(result) - 1) == 13)
octstr_delete(result, octstr_len(result) - 1, 1);
unlock_in(conn);
return result;
}
Octstr *conn_read_withlen(Connection *conn)
{
Octstr *result = NULL;
unsigned char lengthbuf[4];
long length = 0; /* for compiler please */
int try, retry;
lock_in(conn);
for (try = 1; try <= 2; try++) {
if (try > 1)
unlocked_read(conn);
do {
retry = 0;
/* First get the length. */
if (unlocked_inbuf_len(conn) < 4)
continue;
octstr_get_many_chars(lengthbuf, conn->inbuf, conn->inbufpos, 4);
length = decode_network_long(lengthbuf);
if (length < 0) {
warning(0, "conn_read_withlen: got negative length, skipping");
conn->inbufpos += 4;
retry = 1;
}
} while(retry == 1);
/* Then get the data. */
if (unlocked_inbuf_len(conn) - 4 < length)
continue;
conn->inbufpos += 4;
result = unlocked_get(conn, length);
gw_claim_area(result);
break;
}
unlock_in(conn);
return result;
}
Octstr *conn_read_packet(Connection *conn, int startmark, int endmark)
{
int startpos, endpos;
Octstr *result = NULL;
int try;
lock_in(conn);
for (try = 1; try <= 2; try++) {
if (try > 1)
unlocked_read(conn);
/* Find startmark, and discard everything up to it */
startpos = octstr_search_char(conn->inbuf, startmark, conn->inbufpos);
if (startpos < 0) {
conn->inbufpos = octstr_len(conn->inbuf);
continue;
} else {
conn->inbufpos = startpos;
}
/* Find first endmark after startmark */
endpos = octstr_search_char(conn->inbuf, endmark, conn->inbufpos);
if (endpos < 0)
continue;
result = unlocked_get(conn, endpos - startpos + 1);
gw_claim_area(result);
break;
}
unlock_in(conn);
return result;
}
#ifdef HAVE_LIBSSL
X509 *conn_get_peer_certificate(Connection *conn)
{
/* Don't know if it needed to be locked , but better safe as crash */
lock_out(conn);
lock_in(conn);
if (conn->peer_certificate == NULL && conn->ssl != NULL)
conn->peer_certificate = SSL_get_peer_certificate(conn->ssl);
unlock_in(conn);
unlock_out(conn);
return conn->peer_certificate;
}
/*
* XXX Alex decalred the RSA callback routine static and now we're getting
* warning messages for our automatic compilation tests. So we are commenting
* the function out to avoid the warnings.
*
static RSA *tmp_rsa_callback(SSL *ssl, int export, int key_len)
{
static RSA *rsa = NULL;
debug("gwlib.http", 0, "SSL: Generating new RSA key (export=%d, keylen=%d)", export, key_len);
if (export) {
rsa = RSA_generate_key(key_len, RSA_F4, NULL, NULL);
} else {
debug("gwlib.http", 0, "SSL: Export not set");
}
return rsa;
}
*/
static Mutex **ssl_static_locks = NULL;
/* the call-back function for the openssl crypto thread locking */
static void openssl_locking_function(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
mutex_lock(ssl_static_locks[n-1]);
else
mutex_unlock(ssl_static_locks[n-1]);
}
void openssl_init_locks(void)
{
int c, maxlocks = CRYPTO_num_locks();
gw_assert(ssl_static_locks == NULL);
ssl_static_locks = gw_malloc(sizeof(Mutex *) * maxlocks);
for (c = 0; c < maxlocks; c++)
ssl_static_locks[c] = mutex_create();
/* after the mutexes have been created, apply the call-back to it */
CRYPTO_set_locking_callback(openssl_locking_function);
CRYPTO_set_id_callback((CRYPTO_CALLBACK_PTR)gwthread_self);
}
void openssl_shutdown_locks(void)
{
int c, maxlocks = CRYPTO_num_locks();
gw_assert(ssl_static_locks != NULL);
/* remove call-back from the locks */
CRYPTO_set_locking_callback(NULL);
for (c = 0; c < maxlocks; c++)
mutex_destroy(ssl_static_locks[c]);
gw_free(ssl_static_locks);
ssl_static_locks = NULL;
}
void conn_init_ssl(void)
{
SSL_library_init();
SSL_load_error_strings();
global_ssl_context = SSL_CTX_new(SSLv23_client_method());
SSL_CTX_set_mode(global_ssl_context,
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
}
void server_ssl_init(void)
{
SSLeay_add_ssl_algorithms();
SSL_load_error_strings();
global_server_ssl_context = SSL_CTX_new(SSLv23_server_method());
SSL_CTX_set_mode(global_server_ssl_context,
SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
if (!SSL_CTX_set_default_verify_paths(global_server_ssl_context)) {
panic(0, "can not set default path for server");
}
}
void conn_shutdown_ssl(void)
{
if (global_ssl_context)
SSL_CTX_free(global_ssl_context);
ERR_free_strings();
EVP_cleanup();
}
void server_shutdown_ssl(void)
{
if (global_server_ssl_context)
SSL_CTX_free(global_server_ssl_context);
ERR_free_strings();
EVP_cleanup();
}
void use_global_client_certkey_file(Octstr *certkeyfile)
{
SSL_CTX_use_certificate_file(global_ssl_context,
octstr_get_cstr(certkeyfile),
SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(global_ssl_context,
octstr_get_cstr(certkeyfile),
SSL_FILETYPE_PEM);
if (SSL_CTX_check_private_key(global_ssl_context) != 1)
panic(0, "reading global client certificate file `%s', the certificate "
"isn't consistent with the private key (or failed reading the file)",
octstr_get_cstr(certkeyfile));
info(0, "Using global SSL certificate and key from file `%s'",
octstr_get_cstr(certkeyfile));
}
void use_global_server_certkey_file(Octstr *certfile, Octstr *keyfile)
{
SSL_CTX_use_certificate_file(global_server_ssl_context,
octstr_get_cstr(certfile),
SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(global_server_ssl_context,
octstr_get_cstr(keyfile),
SSL_FILETYPE_PEM);
if (SSL_CTX_check_private_key(global_server_ssl_context) != 1) {
error(0, "SSL: %s", ERR_error_string(ERR_get_error(), NULL));
panic(0, "reading global server certificate file %s, the certificate \
isn't consistent with the private key in file %s \
(or failed reading the file)",
octstr_get_cstr(certfile), octstr_get_cstr(keyfile));
}
info(0, "Using global server SSL certificate from file `%s'", octstr_get_cstr(certfile));
info(0, "Using global server SSL key from file `%s'", octstr_get_cstr(keyfile));
}
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
char subject[256];
char issuer [256];
char *status;
X509_NAME_oneline(X509_get_subject_name(ctx->current_cert), subject, sizeof(subject));
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), issuer, sizeof (issuer));
status = preverify_ok ? "Accepting" : "Rejecting";
info(0, "%s certificate for \"%s\" signed by \"%s\"", status, subject, issuer);
return preverify_ok;
}
void use_global_trusted_ca_file(Octstr *ssl_trusted_ca_file)
{
if (ssl_trusted_ca_file != NULL) {
if (!SSL_CTX_load_verify_locations(global_ssl_context,
octstr_get_cstr(ssl_trusted_ca_file),
NULL)) {
panic(0, "Failed to load SSL CA file: %s", octstr_get_cstr(ssl_trusted_ca_file));
} else {
info(0, "Using CA root certificates from file %s",
octstr_get_cstr(ssl_trusted_ca_file));
SSL_CTX_set_verify(global_ssl_context,
SSL_VERIFY_PEER,
verify_callback);
}
} else {
SSL_CTX_set_verify(global_ssl_context,
SSL_VERIFY_NONE,
NULL);
}
}
void conn_config_ssl (CfgGroup *grp)
{
Octstr *ssl_client_certkey_file = NULL;
Octstr *ssl_server_cert_file = NULL;
Octstr *ssl_server_key_file = NULL;
Octstr *ssl_trusted_ca_file = NULL;
/*
* check if SSL is desired for HTTP servers and then
* load SSL client and SSL server public certificates
* and private keys
*/
ssl_client_certkey_file = cfg_get(grp, octstr_imm("ssl-client-certkey-file"));
if (ssl_client_certkey_file != NULL)
use_global_client_certkey_file(ssl_client_certkey_file);
ssl_server_cert_file = cfg_get(grp, octstr_imm("ssl-server-cert-file"));
ssl_server_key_file = cfg_get(grp, octstr_imm("ssl-server-key-file"));
if (ssl_server_cert_file != NULL && ssl_server_key_file != NULL) {
use_global_server_certkey_file(ssl_server_cert_file,
ssl_server_key_file);
}
ssl_trusted_ca_file = cfg_get(grp, octstr_imm("ssl-trusted-ca-file"));
use_global_trusted_ca_file(ssl_trusted_ca_file);
octstr_destroy(ssl_client_certkey_file);
octstr_destroy(ssl_server_cert_file);
octstr_destroy(ssl_server_key_file);
octstr_destroy(ssl_trusted_ca_file);
}
SSL *conn_get_ssl(Connection *conn)
{
if (conn != NULL)
return conn->ssl;
else
return NULL;
}
#else
void conn_config_ssl (CfgGroup *grp)
{
info(0, "SSL not supported, no SSL initialization done.");
}
#endif /* HAVE_LIBSSL */
int conn_get_id(Connection *conn) {
if(conn == NULL)
return 0;
else
return conn->fd;
}