/*
 * $Id: tls_server.c 1782 2007-03-09 13:04:51Z bogdan_iancu $
 *
 * Copyright (C) 2001-2003 FhG Fokus
 * Copyright (C) 2004,2005 Free Software Foundation, Inc.
 * Copyright (C) 2006 enum.at
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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, MA  02111-1307  USA
 */

#include "../dprint.h"
#include "tls_server.h"
#include "tls_config.h"
#include "tls_init.h"
#include "tls_domain.h"
#include "../ip_addr.h"
#include <sys/poll.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "../mem/shm_mem.h"
#include "../timer.h"
#include "../usr_avp.h"
#include "../ut.h"

/*
 * Open questions:
 *
 * - what would happen when select exits, connection is passed
 *   to reader to perform read, but another process would acquire
 *   the same connection meanwhile, performs a write and finishes
 *   accept/connect on behalf of the reader process, thus the
 *   reader process would have nothing to read ? (resolved)
 *
 * - What happens if SSL_accept or SSL_connect gets called on
 *   already established connection (c->S_CONN_OK) ? We could
 *   save some locking provided that the functions do not screw
 *   up the connection (in tcp_fix_read_conn we would not have
 *   to lock before the switch).
 *
 * - tls_blocking_write needs fixing..
 *
 * - we need to protect ctx by a lock -- it is in shared memory
 *   and may be accessed simultaneously
 */


/*
 * Update ssl structure with new fd 
 */
static int
tls_update_fd(struct tcp_connection *c, int fd)
{
	/*
	* must be run from within a lock 
	*/
	SSL            *ssl;

	ssl = (SSL *) c->extra_data;

	if (!SSL_set_fd(ssl, fd)) {
		LOG(L_ERR, "tls_update_fd: Error while assigning socket to ssl\n");
		return -1;
	}

	DBG("tls_update_fd: New fd is %d\n", fd);
	return 0;
}


/*
 * dump ssl error stack 
 */
void
tls_print_errstack(void)
{
	int             code;

	while ((code = ERR_get_error())) {
		LOG(L_ERR, "tls_error: %s\n", ERR_error_string(code, 0));
	}
}

static void tls_dump_cert_info(char* s,	X509* cert)
{
	char* subj;
	char* issuer;

	subj   = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
	issuer = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);

	DBG("%s subject:%s\n", s ? s : "", subj);
	DBG("%s issuer: %s\n", s ? s : "", issuer);
	OPENSSL_free(subj);
	OPENSSL_free(issuer);
}


static void tls_dump_verification_failure(long verification_result)
{
	switch(verification_result) {
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
		DBG("verification failure: unable to get issuer certificate\n");
		break;
	case X509_V_ERR_UNABLE_TO_GET_CRL:
		DBG("verification failure: unable to get certificate CRL\n");
		break;
	case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
		DBG("verification failure: unable to decrypt certificate's signature\n");
		break;
	case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
		DBG("verification failure: unable to decrypt CRL's signature\n");
		break;
	case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
		DBG("verification failure: unable to decode issuer public key\n");
		break;
	case X509_V_ERR_CERT_SIGNATURE_FAILURE:
		DBG("verification failure: certificate signature failure\n");
		break;
	case X509_V_ERR_CRL_SIGNATURE_FAILURE:
		DBG("verification failure: CRL signature failure\n");
		break;
	case X509_V_ERR_CERT_NOT_YET_VALID:
		DBG("verification failure: certificate is not yet valid\n");
		break;
	case X509_V_ERR_CERT_HAS_EXPIRED:
		DBG("verification failure: certificate has expired\n");
		break;
	case X509_V_ERR_CRL_NOT_YET_VALID:
		DBG("verification failure: CRL is not yet valid\n");
		break;
	case X509_V_ERR_CRL_HAS_EXPIRED:
		DBG("verification failure: CRL has expired\n");
		break;
	case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
		DBG("verification failure: format error in certificate's notBefore field\n");
		break;
	case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
		DBG("verification failure: format error in certificate's notAfter field\n");
		break;
	case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
		DBG("verification failure: format error in CRL's lastUpdate field\n");
		break;
	case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
		DBG("verification failure: format error in CRL's nextUpdate field\n");
		break;
	case X509_V_ERR_OUT_OF_MEM:
		DBG("verification failure: out of memory\n");
		break;
	case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
		DBG("verification failure: self signed certificate\n");
		break;
	case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
		DBG("verification failure: self signed certificate in certificate chain\n");
		break;
	case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
		DBG("verification failure: unable to get local issuer certificate\n");
		break;
	case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
		DBG("verification failure: unable to verify the first certificate\n");
		break;
	case X509_V_ERR_CERT_CHAIN_TOO_LONG:
		DBG("verification failure: certificate chain too long\n");
		break;
	case X509_V_ERR_CERT_REVOKED:
		DBG("verification failure: certificate revoked\n");
		break;
	case X509_V_ERR_INVALID_CA:
		DBG("verification failure: invalid CA certificate\n");
		break;
	case X509_V_ERR_PATH_LENGTH_EXCEEDED:
		DBG("verification failure: path length constraint exceeded\n");
		break;
	case X509_V_ERR_INVALID_PURPOSE:
		DBG("verification failure: unsupported certificate purpose\n");
		break;
	case X509_V_ERR_CERT_UNTRUSTED:
		DBG("verification failure: certificate not trusted\n");
		break;
	case X509_V_ERR_CERT_REJECTED:
		DBG("verification failure: certificate rejected\n");
		break;
	case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
		DBG("verification failure: subject issuer mismatch\n");
		break;
	case X509_V_ERR_AKID_SKID_MISMATCH:
		DBG("verification failure: authority and subject key identifier mismatch\n");
		break;
	case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
		DBG("verification failure: authority and issuer serial number mismatch\n");
		break;
	case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
		DBG("verification failure: key usage does not include certificate signing\n");
		break;
	case X509_V_ERR_APPLICATION_VERIFICATION:
		DBG("verification failure: application verification failure\n");
		break;
	}
}

/*
 * Wrapper around SSL_accept, returns -1 on error, 0 on success 
 */
static int
tls_accept(struct tcp_connection *c)
{
	int ret, err;
	SSL *ssl;
	X509* cert;

	if (c->state != S_CONN_ACCEPT) {
		LOG(L_ERR,
			"tcp_accept: Invalid connection state (bug in TLS code)\n");
		return -1;
	}

	ssl = (SSL *) c->extra_data;
#ifndef OPENSSL_NO_KRB5
	if ( ssl->kssl_ctx==NULL )
		ssl->kssl_ctx = kssl_ctx_new( );
#endif
	ret = SSL_accept(ssl);
#ifndef OPENSSL_NO_KRB5
	if ( ssl->kssl_ctx ) {
		kssl_ctx_free( ssl->kssl_ctx );
		ssl->kssl_ctx = 0;
	}
#endif
	if (ret > 0) {
		DBG("tls_accept: TLS handshake successful\n");
		c->state = S_CONN_OK;

		DBG("tls_accept: new connection from %s:%d using %s %s %d\n",
			ip_addr2a(&c->rcv.src_ip), c->rcv.src_port,
			SSL_get_cipher_version(ssl), SSL_get_cipher_name(ssl),
			SSL_get_cipher_bits(ssl, 0)
			);
		DBG("tls_accept: local socket: %s:%d\n",
			ip_addr2a(&c->rcv.dst_ip), c->rcv.dst_port
			);
		cert = SSL_get_peer_certificate(ssl);
		if (cert != 0) {
			tls_dump_cert_info("tls_accept: client certificate", cert);
			if (SSL_get_verify_result(ssl) != X509_V_OK) {
				LOG(L_WARN, "WARNING: tls_accept: client certificate "
					"verification failed!!!\n");
				tls_dump_verification_failure(SSL_get_verify_result(ssl));
			}
			X509_free(cert);
		} else {
			LOG(L_INFO, "tls_accept: client did not present a certificate\n");
		}
		cert = SSL_get_certificate(ssl);
		if (cert != 0) {
			tls_dump_cert_info("tls_accept: local (server) certificate", cert);
		} else {
			/* this should not happen, servers always present a cert */
			LOG(L_ERR, "tls_accept: ERRROR: local TLS server domain has no "
				"certificate\n");
		}
		return 0;
	} else {
		err = SSL_get_error(ssl, ret);
		switch (err) {
			case SSL_ERROR_ZERO_RETURN:
				DBG("tls_accept: SSH handshake failed cleanly\n");
				c->state = S_CONN_BAD;
				return -1;
		
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				/*
				* nothing to do here 
				*/
				return 0;
		
			default:
				LOG(L_ERR, "tls_accept: Error in SSL:\n");
				c->state = S_CONN_BAD;
				tls_print_errstack();
				return -1;
		}
	}

	LOG(L_ERR, "tls_accept: Bug in tls_accept\n");
	return -1;
}


/*
 * wrapper around SSL_connect, returns 0 on success, -1 on error 
 */
static int
tls_connect(struct tcp_connection *c)
{
	int ret, err;
	SSL *ssl;
	X509* cert;

	if (c->state != S_CONN_CONNECT) {
		LOG(L_ERR,
			"tls_connect: Invalid connection state (bug in TLS code)\n");
		return -1;
	}

	ssl = (SSL *) c->extra_data;

	ret = SSL_connect(ssl);
	if (ret > 0) {
		DBG("tls_connect: SSL/TLS connect successuful\n");
		c->state = S_CONN_OK;
		DBG("tls_connect: new connection to %s:%d using %s %s %d\n",
			ip_addr2a(&c->rcv.src_ip), c->rcv.src_port,
			SSL_get_cipher_version(ssl), SSL_get_cipher_name(ssl),
			SSL_get_cipher_bits(ssl, 0)
			);
		DBG("tls_connect: sending socket: %s:%d \n",
			ip_addr2a(&c->rcv.dst_ip), c->rcv.dst_port
			);
		cert = SSL_get_peer_certificate(ssl);
		if (cert != 0) {
			tls_dump_cert_info("tls_connect: server certificate", cert);
			if (SSL_get_verify_result(ssl) != X509_V_OK) {
				LOG(L_WARN, "WARNING: tls_connect: server certificate "
					"verification failed!!!\n");
				tls_dump_verification_failure(SSL_get_verify_result(ssl));
			}
			X509_free(cert);
		} else {
			/* this should not happen, servers always present a cert */
			LOG(L_ERR, "tls_connect: ERRROR: server did not present a "
				"certificate\n");
		}
		cert = SSL_get_certificate(ssl);
		if (cert != 0) {
			tls_dump_cert_info("tls_connect: local (client) certificate",
				cert);
		} else {
			LOG(L_INFO, "tls_connect: local TLS client domain does not have "
				"a certificate\n");
		}
		return 0;
	} else {
		err = SSL_get_error(ssl, ret);
		switch (err) {
			case SSL_ERROR_ZERO_RETURN:
				DBG("tls_connect: SSL_connect failed cleanly\n");
				c->state = S_CONN_BAD;
				return -1;
		
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				/*
				* nothing to do here 
				*/
				return 0;
		
			default:
				LOG(L_ERR, "tls_connect: Error in SSL:\n");
				c->state = S_CONN_BAD;
				tls_print_errstack();
				return -1;
		}
	}

	LOG(L_ERR, "tls_connect: Bug in tls_connect\n");
	return -1;
}

/*
 * wrapper around SSL_shutdown, returns -1 on error, 0 on success 
 */
static int
tls_shutdown(struct tcp_connection *c)
{
	int             ret,
					err;
	SSL            *ssl;

	/*
	* we do not implement full ssl shutdown 
	*/
	ssl = (SSL *) c->extra_data;
	if (ssl == 0) {
		LOG(L_ERR, "tls_shutdown: No ssl data\n");
		return -1;
	}

	ret = SSL_shutdown(ssl);
	if (ret == 1) {
		DBG("tls_shutdown: Shutdown successful\n");
		return 0;
	} else if (ret == 0) {
		DBG("tls_shutdown: First phase of 2-way handshake completed "
			"succesfuly\n");
		return 0;
	} else {
		err = SSL_get_error(ssl, ret);
		switch (err) {
			case SSL_ERROR_ZERO_RETURN:
				c->state = S_CONN_EOF;
				return 0;
		
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				c->state = S_CONN_EOF;
				return 0;
		
			default:
				LOG(L_ERR, "tls_shutdown: Error in SSL:\n");
				c->state = S_CONN_BAD;
				tls_print_errstack();
				return -1;
		}
	}
	
	LOG(L_ERR, "tls_shutdown: Bug in tls_shutdown\n");
	return -1;
}


/*
 * Wrapper around SSL_write, returns number of bytes written on success, * 
 * -1 on error, 0 when it would block 
 */
static int
tls_write(struct tcp_connection *c, int fd, const void *buf, size_t len)
{
	int             ret,
					err;
	/*
	* runs within write lock, no need to lock here 
	*/
	SSL            *ssl;

	ssl = (SSL *) c->extra_data;

	ret = SSL_write(ssl, buf, len);
	if (ret > 0) {
		DBG("tls_write: Write was successful (%d bytes)\n", ret);
		return ret;
	} else {
		err = SSL_get_error(ssl, ret);
		switch (err) {
		case SSL_ERROR_ZERO_RETURN:
			DBG("tls_write: Connection closed cleanly\n");
			c->state = S_CONN_EOF;
			return -1;
	
		case SSL_ERROR_WANT_READ:
		case SSL_ERROR_WANT_WRITE:
			return 0;
	
		default:
			LOG(L_ERR, "tls_write: Error in SSL:\n");
			c->state = S_CONN_BAD;
			tls_print_errstack();
			return -1;
		}
	}

	LOG(L_ERR, "tls_write: Bug in tls_write\n");
	return -1;
}


/*
 * Wrapper around SSL_read 
 */
/*
 * returns number of bytes read, 0 on eof and transits into S_CONN_EOF, -1 
 * on error 
 */
static int
_tls_read(struct tcp_connection *c, void *buf, size_t len)
{
	int             ret,
					err;
	SSL            *ssl;

	ssl = c->extra_data;

	ret = SSL_read(ssl, buf, len);
	if (ret > 0) {
		DBG("_tls_read: %d bytes read\n", ret);
		return ret;
	} else {
		err = SSL_get_error(ssl, ret);
		switch (err) {
		case SSL_ERROR_ZERO_RETURN:
			DBG("_tls_read: Connection closed cleanly\n");
			/*
			* mark end of file 
			*/
			c->state = S_CONN_EOF;
			return 0;
	
		case SSL_ERROR_WANT_READ:
		case SSL_ERROR_WANT_WRITE:
			return 0;
	
		default:
			LOG(L_ERR, "_tls_read: Error in SSL:\n");
			c->state = S_CONN_BAD;
			tls_print_errstack();
			return -1;
		}
	}

	LOG(L_ERR, "_tls_read: Bug in _tls_read\n");
	return -1;
}



/*
 * Called when new tcp connection is accepted or connected, create ssl
 * data structures here, there is no need to acquire any lock, because the 
 * connection is being created by a new process and on other process has
 * access to it yet, this is called before adding the tcp_connection
 * structure into the hash 
 */
int
tls_tcpconn_init(struct tcp_connection *c, int sock)
{
	struct tls_domain *dom;

	struct usr_avp *avp;
	int_str val;
	int_str avp_tlscdom_name;
	unsigned short avp_tlscdom_name_type;

	// we use integer name avp, configured via openser.cfg
	avp_tlscdom_name.n = tls_client_domain_avp;
	avp_tlscdom_name_type = 0;

	/*
	* new connection within a single process, no lock necessary 
	*/
	DBG("tls_tcpconn_init: Entered: Creating a whole new ssl connection\n");
	
	/*
	* do everything tcpconn_new wouldn't do when TLS 
	*/
	c->type = PROTO_TLS;
	c->rcv.proto = PROTO_TLS;
	c->flags = 0;
	c->timeout = get_ticks() + DEFAULT_TCP_CONNECTION_LIFETIME;

	if (c->state == S_CONN_ACCEPT) {
		DBG("tls_tcpconn_init: Looking up socket based TLS server "
			"domain [%s:%d]\n", ip_addr2a(&c->rcv.dst_ip), c->rcv.dst_port);
		dom = tls_find_server_domain(&c->rcv.dst_ip, c->rcv.dst_port);
		if (dom) {
			DBG("tls_tcpconn_init: Found socket based TLS server domain "
				"[%s:%d]\n", ip_addr2a(&dom->addr), dom->port);
				c->extra_data = SSL_new(dom->ctx);
		} else {
			LOG(L_ERR,"tls_tcpconn_init: ERROR: no TLS server domain found\n");
			return -1;
		}
	} else if (c->state == S_CONN_CONNECT) {
		avp = NULL;
		if (avp_tlscdom_name.n) {
			avp = search_first_avp(avp_tlscdom_name_type, avp_tlscdom_name,
				&val, 0);
		} else {
			DBG("tls_tcpconn_init: name based TLS client domains are"
				" disabled\n");
		}
		if (!avp) {
			DBG("tls_tcpconn_init: no TLS client doman AVP set, looking "
				"for socket based TLS client domain\n");
			dom = tls_find_client_domain(&c->rcv.src_ip, c->rcv.src_port);
			if (dom) {
				DBG("tls_tcpconn_init: Found socket based TLS client domain "
					"[%s:%d]\n", ip_addr2a(&dom->addr), dom->port);
					c->extra_data = SSL_new(dom->ctx);
			} else {
				LOG(L_ERR,"tls_tcpconn_init: ERROR: no TLS client "
					"domain found\n");
				return -1;
			}
		} else {
			DBG("tls_tcpconn_init: TLS client domain AVP found = '%.*s'\n",
				val.s.len, ZSW(val.s.s));
			dom = tls_find_client_domain_name(val.s);
			if (dom) {
				DBG("tls_tcpconn_init: Found name based TLS client domain "
					"'%.*s'\n", val.s.len, ZSW(val.s.s));
				c->extra_data = SSL_new(dom->ctx);
			} else {
				DBG("tls_tcpconn_init: No name based TLS client domain found, "
					"trying socket based TLS client domains\n");
				dom = tls_find_client_domain(&c->rcv.src_ip, c->rcv.src_port);
				if (dom) {
					DBG("tls_tcpconn_init: Found socket based TLS client "
						"domain [%s:%d]\n",
					ip_addr2a(&dom->addr), dom->port);
					c->extra_data = SSL_new(dom->ctx);
				} else {
					LOG(L_ERR,"tls_tcpconn_init: ERROR: no TLS client "
						"domain found\n");
					return -1;
				}
			}
		}
	} else {
		LOG(L_ERR,"ERROR:tls_tcpconn_init: Invalid connection "
			"state (bug in TCP code)\n");
		return -1;
	}
	if (!c->extra_data) {
		LOG(L_ERR,"ERROR:tls_tcpconn_init: failed to create SSL structure\n");
		return -1;
	}

#ifndef OPENSSL_NO_KRB5
	if ( ((SSL *)c->extra_data)->kssl_ctx ) {
		kssl_ctx_free( ((SSL *)c->extra_data)->kssl_ctx );
		((SSL *)c->extra_data)->kssl_ctx = 0;
	}
#endif

	if (c->state == S_CONN_ACCEPT) {
		DBG("tls_tcpconn_init: Setting in ACCEPT mode (server)\n");
		SSL_set_accept_state((SSL *) c->extra_data);
	} else if (c->state == S_CONN_CONNECT) {
		DBG("tls_tcpconn_init: Setting in CONNECT mode (client)\n");
		SSL_set_connect_state((SSL *) c->extra_data);
	}
	return 0;
}


/*
 * clean the extra data upon connection shut down 
 */
void
tls_tcpconn_clean(struct tcp_connection *c)
{
	/*
	* runs within global tcp lock 
	*/
	DBG("tls_tcpconn_clean: Entered\n");

	if (c->extra_data) {
		SSL_free((SSL *) c->extra_data);
		c->extra_data = 0;
	}
}


/*
 * perform one-way shutdown, do not wait fro notify from the remote peer 
 */
void
tls_close(struct tcp_connection *c, int fd)
{
	/*
	* runs within global tcp lock 
	*/
	DBG("tls_close: Closing SSL connection\n");	
	tls_update_fd(c, fd);
	tls_shutdown(c);
}



/*
 * This is shamelessly stolen tsend_stream from tsend.c 
 */
/*
 * fixme: probably does not work correctly 
 */
size_t
tls_blocking_write(struct tcp_connection *c, int fd, const char *buf,
		   size_t len)
{
	int             written,
					n;
	int             timeout;
	struct pollfd   pf;
	pf.fd = fd;
	pf.events = POLLOUT | POLLIN;	/* we need both because of ssl
					 * library */

	/* DBG("tls_blocking_write: Entered\n"); //noisy */

	written = 0;

	if (tls_update_fd(c, fd) < 0)
		goto error;

	timeout = tls_send_timeout;
again:
	n = 0;
	switch (c->state) {
		case S_CONN_ACCEPT:
			if (tls_accept(c) < 0)
				goto error;
			timeout = tls_handshake_timeout * 1000;
			break;
	
		case S_CONN_CONNECT:
			if (tls_connect(c) < 0)
				goto error;
			timeout = tls_handshake_timeout * 1000;
			break;
	
		case S_CONN_OK:
			n = tls_write(c, fd, buf, len);
			timeout = tls_send_timeout * 1000;
			break;
	
		default:
			LOG(L_ERR, "tls_blocking_write: Broken connection\n");
			goto error;
	}

	if (n < 0) {
		LOG(L_ERR, "tls_blocking_write: failed to send data\n");
		goto error;
	}

	written += n;
	if (n < len) {
		/*
		* partial write 
		*/
		buf += n;
		len -= n;
	} else {
		/*
		* successful full write 
		*/
		return written;
	}

poll_loop:
	while (1) {
		/*
		* keep tls_send_timeout in seconds to be compatible with
		* tcp_send_timeout 
		*/
		n = poll(&pf, 1, timeout);
		if (n < 0) {
			if (errno == EINTR)
				continue;	/* signal, ignore */
			else if (errno != EAGAIN && errno != EWOULDBLOCK) {
				LOG(L_ERR, "tls_blocking_write: poll failed: %s [%d]\n",
						strerror(errno), errno);
				goto error;
			} else
				goto poll_loop;
		} else if (n == 0) {
			/*
			* timeout 
			*/
			LOG(L_ERR, "tls_blocking_write: send timeout (%d)\n", timeout);
			goto error;
		}
		if (pf.revents & POLLOUT || pf.revents & POLLIN) {
			/*
			* we can read or write again 
			*/
			goto again;
		} else if (pf.revents & (POLLERR | POLLHUP | POLLNVAL)) {
			LOG(L_ERR, "tls_blocking_write: bad poll flags %x\n",
					pf.revents);
			goto error;
		}
		/*
		* if POLLPRI or other non-harmful events happened, continue (
		* although poll should never signal them since we're not
		* interested in them => we should never reach this point) 
		*/
}

error:
	return -1;
}


/*
 * called only when a connection is in S_CONN_OK, we do not have to care
 * about accepting or connecting here, each modification of ssl data
 * structures has to be protected, another process might ask for the same
 * connection and attempt write to it which would result in updating the
 * ssl structures 
 */
size_t
tls_read(struct tcp_connection * c)
{
	/*
	* no lock acquired 
	*/
	/*
	* shamelessly stolen from tcp_read 
	*/
	int             bytes_free;
	struct tcp_req *r;
	int             fd,
					read;

	r = &c->req;
	fd = c->fd;
	bytes_free = TCP_BUF_SIZE - (int) (r->pos - r->buf);

	if (bytes_free == 0) {
		LOG(L_ERR, "tls_read: buffer overrun, dropping\n");
		r->error = TCP_REQ_OVERRUN;
		return -1;
	}

	/*
	* ssl structures may be accessed from several processes, we need to
	* protect each access and modification by a lock 
	*/
	lock_get(&c->write_lock);
	tls_update_fd(c, fd);
	read = _tls_read(c, r->pos, bytes_free);
	lock_release(&c->write_lock);
	if (read > 0)
		r->pos += read;
	return read;
}


/*
 * called before tls_read, the this function should attempt tls_accept or
 * tls_connect depending on the state of the connection, if this function
 * does not transit a connection into S_CONN_OK then tcp layer would not
 * call tcp_read 
 */
int
tls_fix_read_conn(struct tcp_connection *c)
{
	/*
	* no lock acquired 
	*/
	int             ret;

	ret = 0;

	/*
	* We have to acquire the lock before testing c->state, otherwise a
	* writer could modify the structure if it gets preempted and has
	* something to write 
	*/
	lock_get(&c->write_lock);
    switch (c->state) {
		case S_CONN_ACCEPT:
			ret = tls_update_fd(c, c->fd);
			if (!ret) 
				ret = tls_accept(c);
			break;
	
		case S_CONN_CONNECT:
			ret = tls_update_fd(c, c->fd);
			if (!ret)
				ret = tls_connect(c);
			break;
	
		default:	/* fall through */
			break;
	}
	lock_release(&c->write_lock);

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1