/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

/*
 * Purpose:  Implementation of Secure Socket Layer (SSL).
 *
 * This is an "SSL proxy" for Erlang in the form of a port
 * program. 
 *
 * The implementation has borrowed somewhat from the original
 * implementation of `socket' by Claes Wikström, and the former
 * implementation of `ssl_socket' by Helen Ariyan.
 *
 * All I/O is now non-blocking. 
 *
 * When a connection (cp) is in the state JOINED we have the following
 * picture:
 *
 *            proxy->fd                          fd
 *               |                               |
 *  proxy->eof   |  -------->  wq  ----------->  |   bp 
 *               |                               |
 *  Erlang       |                               |   SSL
 *               |                               |
 *  proxy->bp    |  <------ proxy->wq ---------  |   eof
 *               |                               |
 * 
 * We read from Erlang (proxy->fd) and write to SSL (fd); and read from 
 * SSL (fd) and write to Erlang (proxy->fd).  
 *
 * The variables bp (broken pipe) and eof (end of file) take the
 * values 0 and 1.
 *
 * What has been read and cannot be immediately written is put in a
 * write queue (wq). A wq is emptied before reads are continued, which
 * means that at most one chunk that is read can be in a wq.
 *
 * The proxy-to-ssl part of a cp is valid iff 
 *
 *         !bp && (wq.len > 0 || !proxy->eof).
 *
 * The ssl-to-proxy part of a cp is valid iff 
 *
 *         !proxy->bp && (proxy->wq.len > 0 || !eof). 
 *
 * The connection is valid if any of the above parts are valid, i.e.
 * invalid if both parts are invalid.
 *
 * Every SELECT_TIMEOUT second we try to write to those file
 * descriptors that have non-empty wq's (the only way to detect that a
 * far end has gone away is to write to it).
 *
 * STATE TRANSITIONS
 *
 * Below (*) means that the corresponding file descriptor is published
 * (i.e. kwown outside this port program) when the state is entered,
 * and thus cannot be closed without synchronization with the
 * ssl_server.
 *
 * Listen:
 *
 * STATE_NONE ---> (*) PASSIVE_LISTENING <---> ACTIVE_LISTENING
 *
 * Accept:
 *
 * STATE_NONE ---> SSL_ACCEPT ---> (*) CONNECTED ---> JOINED ---> 
 *  ---> SSL_SHUTDOWN ---> DEFUNCT
 *
 * Connect:
 *
 * STATE_NONE ---> (*) WAIT_CONNECT ---> SSL_CONNECT ---> CONNECTED ---> 
 *  ---> JOINED ---> SSL_SHUTDOWN ---> DEFUNCT
 * 
 * In states where file descriptors has been published, and where
 * something goes wrong, the state of the connection is set to
 * DEFUNCT. A connection in such a state can only be closed by a CLOSE
 * message from Erlang (a reception of such a message is registered in
 * cp->closed). The possible states are: WAIT_CONNECT, SSL_CONNECT,
 * CONNECTED, JOINED, and SSL_SHUTDOWN.
 *
 * A connection in state SSL_ACCEPT can be closed and removed without
 * synchronization.
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef __WIN32__
#include "esock_winsock.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <errno.h>

#ifdef __WIN32__
#include <process.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/time.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <fcntl.h>
#endif

#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff  /* Should be in <netinet/in.h>.  */
#endif

#include "esock.h"
#include "debuglog.h"
#include "esock_utils.h"
#include "esock_ssl.h"
#include "esock_osio.h"
#include "esock_posix_str.h"
#include "esock_poll.h"

#define MAJOR_VERSION   2
#define MINOR_VERSION   0
#define MAXREPLYBUF	256
#define RWBUFLEN	(32*1024)
#define IS_CLIENT       0
#define IS_SERVER       1
#define SELECT_TIMEOUT  2	/* seconds */

#define psx_errstr()	esock_posix_str(sock_errno())
#define ssl_errstr()	esock_ssl_errstr

#define PROXY_TO_SSL_VALID(cp) (!(cp)->bp && \
				((cp)->wq.len > 0 || !(cp)->proxy->eof))

#define SSL_TO_PROXY_VALID(cp) (!(cp)->proxy->bp && \
				((cp)->proxy->wq.len > 0 || !(cp)->eof))

#define JOINED_STATE_INVALID(cp) (!(PROXY_TO_SSL_VALID(cp)) && \
				!(SSL_TO_PROXY_VALID(cp)))
static int loop(void);
static int set_poll_conns(Connection *cp, EsockPoll *ep, int verbose);
static Connection *next_polled_conn(Connection *cp, Connection **cpnext,
				    EsockPoll *ep, int set_wq_fds);

static void leave_joined_state(Connection *cp);
static void do_shutdown(Connection *cp);
static void close_and_remove_connection(Connection *cp);
static int reply(int cmd, char *fmt, ...);
static int input(char *fmt, ...);
static int put_pars(unsigned char *buf, char *fmt, va_list args);
static int get_pars(unsigned char *buf, char *fmt, va_list args);
static FD do_connect(char *lipstring, int lport, char *fipstring, int fport);
static FD do_listen(char *ipstring, int lport, int backlog, int *aport);
static FD do_accept(FD listensock, struct sockaddr *saddr, int *len);
static void print_connections(void);
static int check_num_sock_fds(FD fd); 
static void safe_close(FD fd);
static Connection *new_connection(int state, FD fd);
static Connection *get_connection(FD fd);
static void remove_connection(Connection *conn);
static Proxy *get_proxy_by_peerport(int port);
static Proxy *new_proxy(FD fd);
static void remove_proxy(Proxy *proxy);
static void ensure_write_queue(WriteQueue *wq, int size);
static void clean_up(void);

static Connection  *connections = NULL;
static int num_sock_fds;	/* On UNIX all file descriptors */
static Proxy *proxies = NULL;
static int proxy_listensock = INVALID_FD;
static int proxy_listenport = 0;
static int proxy_backlog = 128;
static int proxysock_last_err = 0;
static int proxysock_err_cnt = 0;
static char rwbuf[RWBUFLEN];
static unsigned char *ebuf = NULL; /* Set by read_ctrl() */

static char *connstr[] = {
    "STATE_NONE", 
    "ACTIVE_LISTENING",
    "PASSIVE_LISTENING",
    "CONNECTED",
    "WAIT_CONNECT",
    "SSL_CONNECT",
    "SSL_ACCEPT",
    "JOINED",
    "SSL_SHUTDOWN",
    "DEFUNCT"
};

static char *originstr[] = {
    "listen",
    "accept",
    "connect"
};

int main(int argc, char **argv) 
{
    char *logfile = NULL;
    int i;
    esock_version *vsn;
    char *ciphers; 
#ifdef __WIN32__
    int pid;
    WORD version;
    WSADATA wsa_data;

    set_binary_mode();
    setvbuf(stderr, NULL, _IONBF, 0);
    /* Two sockets for the stdin socket pipe (local thread). */
    num_sock_fds = 2;		
#else
    pid_t pid;
    num_sock_fds = 3;		/* 0, 1, 2 */
#endif

    pid = getpid();
    i = 1;
    while (i < argc) {
	if (strcmp(argv[i], "-d") == 0) {
	    debug = 1;
	    i++;
	} else if (strcmp(argv[i], "-dm") == 0) {
	    debugmsg = 1;
	    i++;
	} else if (strcmp(argv[i], "-pp") == 0) {
	    i++;
	    proxy_listenport = atoi(argv[i]);
	    i++;
	} else if (strcmp(argv[i], "-pb") == 0) {
	    i++;
	    proxy_backlog = atoi(argv[i]);
	    i++;
	} else if (strcmp(argv[i], "-pv") == 0) {
	    i++;
	    protocol_version = atoi(argv[i]);
	    i++;
	} else if (strcmp(argv[i], "-dd") == 0) {
	    i++;
	    logfile = esock_malloc(strlen(argv[i]) + 64);
	    sprintf(logfile, "%s/ssl_esock.%d.log", argv[i], (int)pid);
	    i++;
	} else if (strcmp(argv[i], "-ersa") == 0) {
	    ephemeral_rsa = 1;
	    i++;
	} else if (strcmp(argv[i], "-edh") == 0) {
	    ephemeral_dh = 1;
	    i++;
	}
    }
    if (debug || debugmsg) {
	DEBUGF(("Starting ssl_esock\n"));
	if (logfile) {
	    open_ssllog(logfile);
#ifndef __WIN32__
	    num_sock_fds++;
#endif
	}
	atexit(close_ssllog);
	DEBUGF(("pid = %d\n", getpid()));
    }
    if (esock_ssl_init() < 0) {
	fprintf(stderr, "esock: Could not do esock_ssl_init\n");
	exit(EXIT_FAILURE);
    }

    atexit(esock_ssl_finish);

#ifdef __WIN32__
    /* Start Windows' sockets */
    version = MAKEWORD(MAJOR_VERSION, MINOR_VERSION);
    if (WSAStartup(version, &wsa_data) != 0) {
	fprintf(stderr, "esock: Could not start up Windows' sockets\n");
	exit(EXIT_FAILURE);
    }
    atexit((void (*)(void))WSACleanup);
    if (LOBYTE(wsa_data.wVersion) < MAJOR_VERSION ||
	(LOBYTE(wsa_data.wVersion) == MAJOR_VERSION && 
	 HIBYTE(wsa_data.wVersion) < MINOR_VERSION)) {
	fprintf(stderr, "esock: Windows socket version error. "
		"Requested version:"
		"%d.%d, version found: %d.%d\n", MAJOR_VERSION, 
		MINOR_VERSION, LOBYTE(wsa_data.wVersion), 
		HIBYTE(wsa_data.wVersion));
	exit(EXIT_FAILURE);
    }
    DEBUGF(("Using Windows socket version: %d.%d\n", 
	   LOBYTE(wsa_data.wVersion), HIBYTE(wsa_data.wVersion)));
    DEBUGF(("Maximum number of sockets available: %d\n", 
	    wsa_data.iMaxSockets));
 
    if (esock_osio_init() < 0) {
	fprintf(stderr, "esock: Could not init osio\n");
	exit(EXIT_FAILURE);
    }
    atexit(esock_osio_finish);
#endif

    /* Create the local proxy listen socket and set it to non-blocking */
    proxy_listensock = do_listen("127.0.0.1", proxy_listenport, 
				 proxy_backlog, &proxy_listenport);
    if (proxy_listensock == INVALID_FD) {
	fprintf(stderr, "esock: Cannot create local listen socket\n");
	exit(EXIT_FAILURE);
    }
    SET_NONBLOCKING(proxy_listensock);
    DEBUGF(("Local proxy listen socket: fd = %d, port = %d\n", 
	   proxy_listensock, proxy_listenport));

    vsn = esock_ssl_version();
    ciphers = esock_ssl_ciphers();

    /* Report: port number of the local proxy listen socket, the native
     * os pid, the compile and lib versions of the ssl library, and 
     * the list of available ciphers. */
    reply(ESOCK_PROXY_PORT_REP, "24sss", proxy_listenport, (int)pid, 
	  vsn->compile_version, vsn->lib_version, ciphers);

    atexit(clean_up);

    loop();

    if (logfile) 
	esock_free(logfile);
    exit(EXIT_SUCCESS);
}


/*
 * Local functions
 *
 */

static int loop(void)
{
    EsockPoll pollfd;
    FD fd, msgsock, listensock, connectsock, proxysock;
    int cc, wc, fport, lport, pport, length, backlog, intref, op;
    int value;
    char *lipstring, *fipstring;
    char *flags;
    char *protocol_vsn, *cipher;
    unsigned char *cert, *bin;
    int certlen, binlen;
    struct sockaddr_in iserv_addr;
    int sret = 1;
    Connection *cp, *cpnext, *newcp;
    Proxy *pp;
    time_t last_time = 0, now = 0;
    int set_wq_fds;

    esock_poll_init(&pollfd);

    while(1) {
	esock_poll_zero(&pollfd);
	esock_poll_fd_set_read(&pollfd, proxy_listensock);
	esock_poll_fd_set_read(&pollfd, local_read_fd);

	set_wq_fds = 0;

	if (sret)		/* sret == 1 the first time. */
	    DEBUGF(("==========LOOP=============\n"));

	cc = set_poll_conns(connections, &pollfd, sret) + 1;

	if (sret) {
	    print_connections();
	    DEBUGF(("Before poll/select: %d descriptor%s (total %d)\n",
		    cc, (cc == 1) ? "" : "s", num_sock_fds));
	}

	sret = esock_poll(&pollfd, SELECT_TIMEOUT);
	if (sret < 0) {
	    DEBUGF(("select/poll error: %s\n", psx_errstr()));
	    continue;
	}
	
	time(&now);
	if (now >= last_time + SELECT_TIMEOUT) {
	    set_wq_fds = 1;
	    last_time = now;
	}
	/*
	 * First accept as many connections as possible on the
	 * proxy listen socket. We record the peer port, which
	 * is later used as a reference for joining a proxy 
	 * connection with a network connection.
	 */

	if (esock_poll_fd_isset_read(&pollfd, proxy_listensock)) {
	    while (1) {
		length = sizeof(iserv_addr);
		proxysock = do_accept(proxy_listensock, 
				      (struct sockaddr *)&iserv_addr, 
				      (int*)&length);
		if(proxysock == INVALID_FD) {
		    if (sock_errno() != ERRNO_BLOCK) {
			/* We can here for example get the error
			 * EMFILE, i.e. no more file descriptors
			 * available, but we do not have any specific
			 * connection to report the error to.  We
			 * increment the error counter and saves the
			 * last err.  
			 */
			proxysock_err_cnt++;
			proxysock_last_err = sock_errno();
			DEBUGF(("accept error (proxy_listensock): %s\n", 
				psx_errstr()));
		    }
		    break;
		} else {
		    /* Get peer port number */
		    length = sizeof(iserv_addr);
		    if (getpeername(proxysock, (struct sockaddr *)&iserv_addr, 
				    &length) < 0) {
			DEBUGF(("Can't get peername of proxy socket"));
			safe_close(proxysock);
		    } else {
			/* Add to pending proxy connections */
			SET_NONBLOCKING(proxysock);
			pp = new_proxy(proxysock);
			pp->peer_port = ntohs(iserv_addr.sin_port);
			DEBUGF(("-----------------------------------\n"));
			DEBUGF(("[PROXY_LISTEN_SOCK] conn accepted: "
				"proxyfd = %d, "
			       "peer port = %d\n", proxysock, pp->peer_port));
		    }
		}
	    }
	}

	/* 
	 * Read control messages from Erlang
	 */
	if (esock_poll_fd_isset_read(&pollfd, local_read_fd)) {  
	    cc = read_ctrl(&ebuf);
	    if ( cc < 0 ) {
		DEBUGF(("Read loop -1 or 0\n"));
		return -1;
	    } else if (cc == 0) {  /* not eof  */
		DEBUGF(("GOT empty string \n"));

	    } else {

		switch((int)*ebuf) {

		case ESOCK_SET_SEED_CMD:
		    /* 
		     * ebuf = {cmd(1), binary(N) }
		     */
		    input("b", &binlen, &bin);
		    DEBUGF(("[SET_SEED_CMD]\n"));
		    esock_ssl_seed(bin, binlen);
		    /* no reply */
		    break;

		case ESOCK_GETPEERNAME_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    DEBUGF(("[GETPEERNAME_CMD] fd = %d\n", fd)); 
		    cp = get_connection(fd);
		    length = sizeof(iserv_addr);
		    if (!cp) {
			sock_set_errno(ERRNO_NOTSOCK);
			reply(ESOCK_GETPEERNAME_ERR, "4s", fd, psx_errstr());
		    } else if (getpeername(fd, 
					   (struct sockaddr *) &iserv_addr, 
					   &length) < 0) {
			reply(ESOCK_GETPEERNAME_ERR, "4s", fd, psx_errstr());
		    } else {
			/*
			 * reply  = {cmd(1), fd(4), port(2), 
			 * 	    ipstring(N), 0(1)}
			 */
			reply(ESOCK_GETPEERNAME_REP, "42s", fd, 
			      ntohs(iserv_addr.sin_port), 
			      inet_ntoa(iserv_addr.sin_addr));
		    }
		    break;

		case ESOCK_GETSOCKNAME_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    DEBUGF(("[GETSOCKNAME_CMD] fd = %d\n", fd)); 
		    cp = get_connection(fd);
		    length = sizeof(iserv_addr);
		    if (!cp) {
			sock_set_errno(ERRNO_NOTSOCK);
			reply(ESOCK_GETSOCKNAME_ERR, "4s", fd, psx_errstr());
		    } else if (getsockname(fd, 
					   (struct sockaddr *)&iserv_addr, 
					   &length) < 0) {
			reply(ESOCK_GETSOCKNAME_ERR, "4s", fd, psx_errstr());
		    } else {
			/*
			 * reply  = {cmd(1), fd(4), port(2), 
			 * 	    ipstring(N), 0(1)}
			 */
			reply(ESOCK_GETSOCKNAME_REP, "42s", fd, 
			      ntohs(iserv_addr.sin_port),
			      inet_ntoa(iserv_addr.sin_addr));
		    }
		    break;

		case ESOCK_GETCONNINFO_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    DEBUGF(("[GETCONNINFO_CMD] fd = %d\n", fd)); 
		    cp = get_connection(fd);
		    if (!cp) {
			sock_set_errno(ERRNO_NOTSOCK);
			reply(ESOCK_GETCONNINFO_ERR, "4s", fd, psx_errstr());
		    } else {
			if (esock_ssl_getprotocol_version(cp,
							  &protocol_vsn) < 0)
			    reply(ESOCK_GETCONNINFO_ERR, "4s", fd, psx_errstr());
			else if (esock_ssl_getcipher(cp, &cipher) < 0)
			    reply(ESOCK_GETCONNINFO_ERR, "4s", fd, psx_errstr());
			else
			/*
			 * reply  = {cmd(1), fd(4), protocol(N), 0(1),
			 * 	    cipher(N), 0(1)}
			 */
			    reply(ESOCK_GETCONNINFO_REP, "4ss", fd, 
				  protocol_vsn, cipher);
		    }
		    break;

		case ESOCK_GETPEERCERT_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    DEBUGF(("[GETPEERCERT_CMD] fd = %d\n", fd)); 
		    cp = get_connection(fd);
		    if (!cp) {
			sock_set_errno(ERRNO_NOTSOCK);
			reply(ESOCK_GETPEERCERT_ERR, "4s", fd, psx_errstr());
		    } else {
			if ((certlen = esock_ssl_getpeercert(cp, &cert)) < 0)
			    reply(ESOCK_GETPEERCERT_ERR, "4s", fd, psx_errstr());
			else {
			    /*
			     * reply  = {cmd(1), fd(4), certlen(4), cert(N)}
			     */
			    reply(ESOCK_GETPEERCERT_REP, "4b", fd, 
				  certlen, cert);
			    esock_free(cert);
			}
		    }
		    break;

		case ESOCK_CONNECT_CMD:
		    /* 
		     * ebuf = {cmd(1), intref(4), 
		     * 	       lport(2), lipstring(N), 0(1),  -- local
		     *         fport(2), fipstring(N), 0(1),  -- foreign
		     * 	       flags(N), 0(1)}
		     */
		    input("42s2ss", &intref, &lport, &lipstring, 
			  &fport, &fipstring, &flags);
		    DEBUGF(("[CONNECT_CMD] intref = %d, "
			    "lipstring = %s lport = %d, "
			    "fipstring = %s fport = %d, "
			    "flags = %s\n", intref, lipstring, lport,
			    fipstring, fport, flags));
		    connectsock = do_connect(lipstring, lport, 
					     fipstring, fport);
		    if(connectsock == INVALID_FD) {
			reply(ESOCK_CONNECT_SYNC_ERR, "4s", intref, psx_errstr());
			break;
		    }
		    DEBUGF(("  fd = %d\n", connectsock));
		    cp = new_connection(ESOCK_WAIT_CONNECT, connectsock);
		    cp->origin = ORIG_CONNECT;
		    length = strlen(flags);
		    cp->flags = esock_malloc(length + 1);
		    strcpy(cp->flags, flags);
		    DEBUGF(("-> WAIT_CONNECT fd = %d\n", connectsock));
		    /* Publish connectsock */
		    reply(ESOCK_CONNECT_WAIT_REP, "44", intref, connectsock);
		    break;
		    
		case ESOCK_TERMINATE_CMD:
		    /* 
		     * ebuf = {cmd(1)}
		     */
		    exit(EXIT_SUCCESS);
		    break;

		case ESOCK_CLOSE_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    if ((cp = get_connection(fd))) {
			DEBUGF(("%s[CLOSE_CMD]: fd = %d\n", 
				connstr[cp->state], fd));
			if (cp->proxy)
			    cp->proxy->bp = 1;
			switch (cp->state) {
			case ESOCK_JOINED:
			    cp->close = 1;
			    if (JOINED_STATE_INVALID(cp))
				leave_joined_state(cp);
			    break;
			case ESOCK_SSL_SHUTDOWN:
			    cp->close = 1;
			    DEBUGF(("  close flag set\n"));
			    break;
			default:
			    DEBUGF(("-> (removal)\n"));
			    close_and_remove_connection(cp);
			}
		    } else 
			DEBUGF(("%s[CLOSE_CMD]: ERROR: fd = %d not found\n",
				connstr[cp->state], fd));
		    break;

		case ESOCK_SET_SOCKOPT_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4), op(1), on(1)}
		     */
		    input("411", &fd, &op, &value);
		    switch(op) {
		    case ESOCK_SET_TCP_NODELAY:
			if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, 
				      (void *)&value, sizeof(value)) < 0) {
			    DEBUGF(("Error: setsockopt TCP_NODELAY\n"));
			    reply(ESOCK_IOCTL_ERR, "4s", fd, psx_errstr());
			} else {
			    reply(ESOCK_IOCTL_OK, "4", fd);
			}
			break;
		    default:
			DEBUGF(("Error: set_sock_opt - Not implemented\n"));
			sock_set_errno(ERRNO_OPNOTSUPP);
			reply(ESOCK_IOCTL_ERR, "4", fd, psx_errstr());
			break;
		    }
		    break;

		case ESOCK_LISTEN_CMD:
		    /* 
		     * ebuf = {cmd(1), intref(4), lport(2), ipstring(N), 0(1),
		     * 	       backlog(2), flags(N), 0(1)}
		     */
		    input("42s2s", &intref, &lport, &lipstring, &backlog,
			  &flags);
		    DEBUGF(("[LISTEN_CMD] intref = %d, port = %d, "
			   "ipstring = %s, backlog = %d, flags = %s\n", 
			   intref, lport, lipstring, backlog, flags));
		    
		    listensock = do_listen(lipstring, lport, backlog, &lport);
		    if(listensock == INVALID_FD) {
			reply(ESOCK_LISTEN_SYNC_ERR, "4s", intref, psx_errstr());
			break;
		    }
		    cp = new_connection(ESOCK_PASSIVE_LISTENING, listensock);
		    /* Flags may be an empty string */
		    if ((length = strlen(flags)) > 0) {
			cp->flags = esock_malloc(length + 1);
			strcpy(cp->flags, flags);
		    }
		    cp->origin = ORIG_LISTEN;
		    if (esock_ssl_listen_init(cp) < 0) {
			DEBUGF(("esock_ssl_listen_init() failed.\n"));
			reply(ESOCK_LISTEN_SYNC_ERR, "4s", intref, 
			      ssl_errstr());
			close_and_remove_connection(cp);
			break;
		    }
		    DEBUGF(("-> PASSIVE_LISTENING (fd = %d)\n", listensock));
		    /* Publish listensock */
		    reply(ESOCK_LISTEN_REP, "442", intref, listensock,
			  ntohs(iserv_addr.sin_port));
		    break;

		case ESOCK_ACCEPT_CMD:
		    /* 
		     * ebuf =  { op(1), fd(4), flags(N), 0(1)} 
		     */
		    input("4s", &fd, &flags);
		    DEBUGF(("[ACCEPT_CMD] listenfd = %d, flags = %s\n", fd, 
			   flags));
		    cp = get_connection(fd);
		    if (cp) {
			 /* We store the flags in the listen socket's 
			  * connection, and overwrite previous flags.
			  */
			if ((length = strlen(flags)) > 0) {
			    if (cp->flags)
				cp->flags = esock_realloc(cp->flags, 
							  length + 1);
			    else
				cp->flags = esock_malloc(length + 1);
			    strcpy(cp->flags, flags);
			}
			if (cp->flags && cp->flags[0] != '\0') {
			    cp->acceptors++;
			    cp->state = ESOCK_ACTIVE_LISTENING; 
			    DEBUGF(("-> ACTIVE_LISTENING\n"));
			    break;
			}
			DEBUGF(("ERROR: flags empty\n"));
		    }
		    reply(ESOCK_ACCEPT_ERR, "4s", fd, "ebadf");
		    break;

		case ESOCK_NOACCEPT_CMD:
		    /* 
		     * ebuf = {cmd(1), fd(4)}
		     */
		    input("4", &fd);
		    DEBUGF(("[NOACCEPT_CMD] listenfd = %d\n", fd));
		    cp = get_connection(fd);
		    if (cp && (--cp->acceptors <= 0)) {
			cp->acceptors = 0;
			cp->state = ESOCK_PASSIVE_LISTENING;
			DEBUGF(("-> PASSIVE_LISTENING\n"));
		    }
		    break;

		case ESOCK_PROXY_JOIN_CMD:
		    /*
		     * ebuf = {cmd(1), fd(4), portnum(2)}
		     *
		     * fd      - file descriptor of a connection in state
		     *           CONNECTED
		     * portnum - port number of the Erlang proxy peer 
		     */
		    input("42", &fd, &pport);
		    cp = get_connection(fd);
		    pp = get_proxy_by_peerport(pport);
		    if (cp && cp->state == ESOCK_CONNECTED && pp) {
			DEBUGF(("CONNECTED[PROXY_JOIN_CMD] fd = %d "
				"portnum = %d\n", fd, pport));
			cp->proxy = pp;
			pp->conn = cp;
			reply(ESOCK_PROXY_JOIN_REP, "4", fd);
			cp->state = ESOCK_JOINED;
			DEBUGF(("-> JOINED\n"));
			break;
		    }
		    if (!cp) {
			DEBUGF(("[PROXY_JOIN_CMD] ERROR: No connection "
				"having fd = %d\n", fd));
			reply(ESOCK_PROXY_JOIN_ERR, "4s", fd, "ebadsocket");
		    } else if (cp->state != ESOCK_CONNECTED) {
			DEBUGF(("%s[PROXY_JOIN_CMD] ERROR: Bad state: "
			       "fd = %d\n", connstr[cp->state], cp->fd));
			reply(ESOCK_PROXY_JOIN_ERR, "4s", fd, "ebadstate");
		    } else {
			DEBUGF(("ERROR: No proxy: fd = %d, pport = %d\n",
			       fd, pport));
			if (proxysock_err_cnt > 0) {
			    proxysock_err_cnt--;
			    reply(ESOCK_PROXY_JOIN_ERR, "4s", fd, 
				  esock_posix_str(proxysock_last_err));
			} else {
			    reply(ESOCK_PROXY_JOIN_ERR, "4s", fd, 
				  "enoproxysocket");
			}
			cp->state = ESOCK_DEFUNCT;
		    }
		    break;

		default:
		    fprintf(stderr, "esock: default value in loop %c\n", 
			    *ebuf);
		    exit(EXIT_FAILURE);
		    break;
		}
	    }
	}

	/* Go through all connections that have their file descriptors
           set. */

	/* Note: We may remove the current connection (cp). Thus we
	 * must be careful not to read cp->next after cp has been
	 * removed.  */
	for (cp = next_polled_conn(connections, &cpnext, &pollfd, set_wq_fds); 
	     cp != NULL; 
	     cp = next_polled_conn(cpnext, &cpnext, &pollfd, set_wq_fds)
	     ) {

	    switch(cp->state) {

	    case ESOCK_PASSIVE_LISTENING:
		DEBUGF(("-----------------------------------\n"));
		fprintf(stderr, "esock: Got connect request while PASSIVE\n");
		exit(EXIT_FAILURE);
		break;
		
	    case ESOCK_ACTIVE_LISTENING:
		/* new connect from network */
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("ACTIVE_LISTENING - trying to accept on %d\n", 
		       cp->fd));
		length = sizeof(iserv_addr);
		msgsock = do_accept(cp->fd, (struct sockaddr*)&iserv_addr, 
				    (int*)&length);
		if(msgsock == INVALID_FD)  {
		    DEBUGF(("accept error: %s\n", psx_errstr()));
		    reply(ESOCK_ACCEPT_ERR, "4s", cp->fd, psx_errstr());
		    break;
		}
		SET_NONBLOCKING(msgsock);
		if (--cp->acceptors <= 0) {
		    cp->acceptors = 0;
		    cp->state = ESOCK_PASSIVE_LISTENING;
		    DEBUGF(("-> PASSIVE_LISTENING\n"));
		}
		DEBUGF(("server accepted connection on fd %d\n", msgsock));
		newcp = new_connection(ESOCK_SSL_ACCEPT, msgsock);
		newcp->origin = ORIG_ACCEPT;
		DEBUGF(("(new) -> SSL_ACCEPT on fd %d\n", msgsock));
		newcp->listen_fd = cp->fd; /* Needed for ESOCK_ACCEPT_ERR  */
		length = strlen(cp->flags);
		/* XXX new flags are not needed */
		newcp->flags = esock_malloc(length + 1);
		strcpy(newcp->flags, cp->flags); /* XXX Why? */
		if (esock_ssl_accept_init(newcp, cp->opaque) < 0) {
		    /* N.B.: The *listen fd* is reported. */
		    reply(ESOCK_ACCEPT_ERR, "4s", newcp->listen_fd,
			  ssl_errstr());
		    close_and_remove_connection(newcp);
		    break;
		}
		newcp->ssl_want = ESOCK_SSL_WANT_READ;
		break;

	    case ESOCK_SSL_ACCEPT:
		/* SSL accept handshake. msgsock is *not* published yet. */
		msgsock = cp->fd;
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("SSL_ACCEPT fd = %d\n", msgsock));
		if (esock_ssl_accept(cp) < 0) {
		    if (sock_errno() != ERRNO_BLOCK) {
			/* Handshake failed. */
			reply(ESOCK_ACCEPT_ERR, "4s", cp->listen_fd,
			      ssl_errstr());
			DEBUGF(("ERROR: handshake: %s\n", ssl_errstr()));
			close_and_remove_connection(cp);
		    }
		} else {
		    /* SSL handshake successful: publish */
		    reply(ESOCK_ACCEPT_REP, "44", cp->listen_fd, msgsock);
		    DEBUGF(("-> CONNECTED\n"));
		    DEBUGF((" Session was %sreused.\n", 
			    (esock_ssl_session_reused(cp)) ? "" : "NOT "));
		    cp->state = ESOCK_CONNECTED;
		}
		break;

	    case ESOCK_CONNECTED:
		/* Should not happen. We do not read or write until
		   the connection is in state JOINED. */
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("CONNECTED: Error: should not happen. fd = %d\n", 
			cp->fd));
		break;

	    case ESOCK_JOINED:
		/* 
		 * Reading from Proxy, writing to SSL 
		 */
		if (esock_poll_fd_isset_write(&pollfd, cp->fd)) {
		    /* If there is a write queue, write to ssl only */
		    if (cp->wq.len > 0) { 
			/* The write retry semantics of SSL_write in
			 * the OpenSSL package is strange. Partial
			 * writes never occur, only complete writes or
			 * failures.  A failure, however, still
			 * consumes all data written, although not all
			 * encrypted data could be written to the
			 * underlying socket. To retry a write we have
			 * to provide the same buf and length as in
			 * the original call, in our case rwbuf and
			 * the original buffer length. Hence the
			 * strange memcpy(). Note that wq.offset will
			 * always be zero when we use OpenSSL.  
			 */
			DEBUGF(("-----------------------------------\n"));
			DEBUGF(("JOINED: writing to ssl "
				"fd = %d, from write queue only, wc = %d\n", 
				cp->fd, cp->wq.len - cp->wq.offset));
			memcpy(rwbuf, cp->wq.buf, cp->wq.len - cp->wq.offset);

			/* esock_ssl_write sets cp->eof, cp->bp when return
			 * value is zero */
			wc = esock_ssl_write(cp, rwbuf, 
					     cp->wq.len - cp->wq.offset);
			if (wc < 0) {
			    if (sock_errno() != ERRNO_BLOCK) {
				/* Assume broken SSL pipe */
				DEBUGF(("broken SSL pipe\n"));
				cp->bp = 1;
				shutdown(cp->proxy->fd, SHUTDOWN_READ);
				cp->proxy->eof = 1;
				if (JOINED_STATE_INVALID(cp)) {
				    leave_joined_state(cp);
				    break;
				}
			    }
			} else if (wc == 0) {
			    /* SSL broken pipe */
			    DEBUGF(("broken SSL pipe\n"));
			    cp->bp = 1;
			    shutdown(cp->proxy->fd, SHUTDOWN_READ);
			    cp->proxy->eof = 1;
			    if (JOINED_STATE_INVALID(cp)) {
				leave_joined_state(cp);
				break;
			    }
			} else {
			    cp->wq.offset += wc;
			    if (cp->wq.offset == cp->wq.len)
				cp->wq.len = 0;
			}
		    }
		} else if (esock_poll_fd_isset_read(&pollfd, cp->proxy->fd)) {
		    /* Read from proxy and write to SSL */
		    DEBUGF(("-----------------------------------\n"));
		    DEBUGF(("JOINED: reading from proxy, "
			   "proxyfd = %d\n", cp->proxy->fd));
		    cc = sock_read(cp->proxy->fd, rwbuf, RWBUFLEN); 
		    DEBUGF(("read from proxyfd = %d, cc = %d\n", 
			   cp->proxy->fd, cc));
		    if (cc > 0) {
			/* esock_ssl_write sets cp->eof, cp->bp when return
			 * value is zero */
			wc = esock_ssl_write(cp, rwbuf, cc);
			if (wc < 0) {
			    if (sock_errno() != ERRNO_BLOCK) {
				/* Assume broken pipe */
				DEBUGF(("broken SSL pipe\n"));
				cp->bp = 1;
				shutdown(cp->proxy->fd, SHUTDOWN_READ);
				cp->proxy->eof = 1;
				if (JOINED_STATE_INVALID(cp)) {
				    leave_joined_state(cp);
				    break;
				}
			    } else {
				/* add to write queue */
				DEBUGF(("adding all to write queue "
					"%d bytes\n", cc));
				ensure_write_queue(&cp->wq, cc);
				memcpy(cp->wq.buf, rwbuf, cc);
				cp->wq.len = cc;
				cp->wq.offset = 0;
			    }
			} else if (wc == 0) {
				/* Broken SSL pipe */
				DEBUGF(("broken SSL pipe\n"));
				cp->bp = 1;
				shutdown(cp->proxy->fd, SHUTDOWN_READ);
				cp->proxy->eof = 1;
				if (JOINED_STATE_INVALID(cp)) {
				    leave_joined_state(cp);
				    break;
				}
			} else if (wc < cc) {
			    /* add remainder to write queue */
			    DEBUGF(("adding remainder to write queue "
				    "%d bytes\n", cc - wc));
			    ensure_write_queue(&cp->wq, cc - wc);
			    memcpy(cp->wq.buf, rwbuf + wc, cc - wc);
			    cp->wq.len = cc - wc;
			    cp->wq.offset = 0;
			} 
		    } else {
			/* EOF proxy or error */
			DEBUGF(("proxy eof or error\n"));
			cp->proxy->eof = 1;
			if (cp->wq.len == 0) {
			    esock_ssl_shutdown(cp);
			    cp->bp = 1;
			}
			if (JOINED_STATE_INVALID(cp)) {
			    leave_joined_state(cp);
			    break;
			}
		    }
		}
		/* 
		 * Reading from SSL, writing to proxy 
		 */
		if (esock_poll_fd_isset_write(&pollfd, cp->proxy->fd)) {
		    /* If there is a write queue, write to proxy only */
		    if (cp->proxy->wq.len > 0) {
			DEBUGF(("-----------------------------------\n"));
			DEBUGF(("JOINED: writing to proxyfd = %d, "
				"from write queue only, wc = %d\n", 
				cp->proxy->fd, cp->proxy->wq.len - 
				cp->proxy->wq.offset));
			wc = sock_write(cp->proxy->fd, cp->proxy->wq.buf + 
					cp->proxy->wq.offset,
					cp->proxy->wq.len - 
					cp->proxy->wq.offset);
			if (wc < 0) {
			    if (sock_errno() != ERRNO_BLOCK) {
				/* Assume broken pipe */
				DEBUGF(("broken proxy pipe\n"));
				cp->proxy->bp = 1;
				/* There is no SSL shutdown for read */
				cp->eof = 1;
				if (JOINED_STATE_INVALID(cp)) {
				    leave_joined_state(cp);
				    break;
				}
			    }
			} else {
			    cp->proxy->wq.offset += wc;
			    if (cp->proxy->wq.offset == cp->proxy->wq.len)
				cp->proxy->wq.len = 0;
			}
		    }
		} else if (esock_poll_fd_isset_read(&pollfd, cp->fd)) {
		    /* Read from SSL and write to proxy */
		    DEBUGF(("-----------------------------------\n"));
		    DEBUGF(("JOINED: read from ssl fd = %d\n",
			   cp->fd));
		    cc = esock_ssl_read(cp, rwbuf, RWBUFLEN);
		    DEBUGF(("read from fd = %d, cc = %d\n", cp->fd, cc));
		    if (cc > 0) {
			wc = sock_write(cp->proxy->fd, rwbuf, cc);
			if (wc < 0) {
			    if (sock_errno() != ERRNO_BLOCK) {
				DEBUGF(("broken proxy pipe\n"));
				/* Assume broken pipe */
				cp->proxy->bp = 1;
				/* There is no SSL shutdown for read */
				cp->eof = 1;
				if (JOINED_STATE_INVALID(cp)) {
				    leave_joined_state(cp);
				    break;
				}
			    } else {
				/* add all to write queue */
				DEBUGF(("adding to write queue %d bytes\n", 
					cc));
				ensure_write_queue(&cp->proxy->wq, cc);
				memcpy(cp->proxy->wq.buf, rwbuf, cc);
				cp->proxy->wq.len = cc;
				cp->proxy->wq.offset = 0;
			    }
			} else if (wc < cc) {
			    /* add to write queue */
			    DEBUGF(("adding to write queue %d bytes\n",
				    cc - wc));
			    ensure_write_queue(&cp->proxy->wq, cc - wc);
			    memcpy(cp->proxy->wq.buf, rwbuf + wc, cc - wc);
			    cp->proxy->wq.len = cc - wc;
			    cp->proxy->wq.offset = 0;
			} 
		    } else if (cc == 0) {
			/* SSL eof */
			DEBUGF(("SSL eof\n"));
			cp->eof = 1;
			if (cp->proxy->wq.len == 0) {
			    shutdown(cp->proxy->fd, SHUTDOWN_WRITE);
			    cp->proxy->bp = 1;
			}
			if (JOINED_STATE_INVALID(cp)) {
			    leave_joined_state(cp);
			    break;
			}
		    } else {
			/* This may very well happen when reading from SSL. */
			DEBUGF(("NOTE: readmask set, cc < 0,  fd = %d, "
				"is ok\n", cp->fd));
		    }
		}
		break;

	    case ESOCK_SSL_SHUTDOWN:
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("SSL_SHUTDOWN: fd = %d\n", cp->fd));
		do_shutdown(cp);
		break;

	    case ESOCK_DEFUNCT:
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("DEFUNCT: ERROR: should not happen. fd = %d\n", 
			cp->fd));
		break;

	    case ESOCK_WAIT_CONNECT:
		/* New connection shows up */
		connectsock = cp->fd;/* Is published */
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("WAIT_CONNECT fd = %d\n", connectsock));

		/* If the connection did succeed it's possible to
		 * fetch the peer name (UNIX); or failure shows in
		 * exceptmask (WIN32). Sorry for the mess below, but
		 * we have to have balanced paren's in #ifdefs in
		 * order not to confuse Emacs' indentation.  */
		length = sizeof(iserv_addr);
		if (
#ifdef __WIN32__
		    esock_poll_fd_isset_exception(&pollfd, connectsock)
#else
		    getpeername(connectsock, (struct sockaddr *)&iserv_addr, 
				&length) < 0
#endif
		    ) {
		    sock_set_errno(ERRNO_CONNREFUSED);
		    DEBUGF(("connect error: %s\n", psx_errstr()));
		    reply(ESOCK_CONNECT_ERR, "4s", connectsock, psx_errstr());
		    cp->state = ESOCK_DEFUNCT;
		    break;
		}
		if (esock_ssl_connect_init(cp) < 0) {
		    DEBUGF(("esock_ssl_connect_init() failed\n"));
		    reply(ESOCK_CONNECT_ERR, "4s", connectsock, ssl_errstr());
		    cp->state = ESOCK_DEFUNCT;
		    break;
		}
		DEBUGF(("-> SSL_CONNECT\n"));
		cp->state = ESOCK_SSL_CONNECT;
		cp->ssl_want = ESOCK_SSL_WANT_WRITE;
		break;

	    case ESOCK_SSL_CONNECT:
		/* SSL connect handshake. connectsock is published. */
		connectsock = cp->fd;
		DEBUGF(("-----------------------------------\n"));
		DEBUGF(("SSL_CONNECT fd = %d\n", connectsock));
		if (esock_ssl_connect(cp) < 0) {
		    if (sock_errno() != ERRNO_BLOCK) {
			/* Handshake failed */
			DEBUGF(("ERROR: handshake: %s\n", ssl_errstr()));
			reply(ESOCK_CONNECT_ERR, "4s", connectsock,
			      ssl_errstr());
			cp->state = ESOCK_DEFUNCT;
		    }
		} else {
		    /* SSL connect handshake successful */
		    DEBUGF(("-> CONNECTED\n"));
		    reply(ESOCK_CONNECT_REP, "4", connectsock);
		    cp->state = ESOCK_CONNECTED;
		}
		break;

	    default:
		DEBUGF(("ERROR: Connection in unknown state.\n"));
	    }
	}
   }
}

static int set_poll_conns(Connection *cp, EsockPoll *ep, int verbose)
{
    int i = 0;
    
    if (verbose)
	DEBUGF(("MASKS SET FOR FD: "));
    while (cp) {
	switch (cp->state) {
	case ESOCK_ACTIVE_LISTENING:
	    if (verbose)
		DEBUGF(("%d (read) ", cp->fd));
	    esock_poll_fd_set_read(ep, cp->fd);
	    break;
	case ESOCK_WAIT_CONNECT:
	    if (verbose)
		DEBUGF(("%d (write) ", cp->fd));
	    esock_poll_fd_set_write(ep, cp->fd);
#ifdef __WIN32__
	    esock_poll_fd_set_exception(ep, cp->fd); /* Failure shows in exceptions */
#endif
	    break;
	case ESOCK_SSL_CONNECT:
	case ESOCK_SSL_ACCEPT:
	    if (cp->ssl_want == ESOCK_SSL_WANT_READ) {
		if (verbose)
		    DEBUGF(("%d (read) ", cp->fd));
		esock_poll_fd_set_read(ep, cp->fd);
	    } else if (cp->ssl_want == ESOCK_SSL_WANT_WRITE) {
		if (verbose)
		    DEBUGF(("%d (write) ", cp->fd));
		esock_poll_fd_set_write(ep, cp->fd);
	    }
	    break;
	case ESOCK_JOINED:
	    if (!cp->bp) {
		if (cp->wq.len) {
		    if (verbose)
			DEBUGF(("%d (write) ", cp->fd));
		    esock_poll_fd_set_write(ep, cp->fd);
		} else if (!cp->proxy->eof) {
		    if (verbose)
			DEBUGF(("%d (read) ", cp->proxy->fd));
		    esock_poll_fd_set_read(ep, cp->proxy->fd);
		}
	    }
	    if (!cp->proxy->bp) {
		if (cp->proxy->wq.len) {
		    if (verbose)
			DEBUGF(("%d (write) ", cp->proxy->fd));
		    esock_poll_fd_set_write(ep, cp->proxy->fd);
		} else if (!cp->eof) {
		    if (verbose)
			DEBUGF(("%d (read) ", cp->fd));
		    esock_poll_fd_set_read(ep, cp->fd);
		}
	    }
	    break;
	case ESOCK_SSL_SHUTDOWN:
	    if (cp->ssl_want == ESOCK_SSL_WANT_READ) {
		if (verbose)
		    DEBUGF(("%d (read) ", cp->fd));
		esock_poll_fd_set_read(ep, cp->fd);
	    } else if (cp->ssl_want == ESOCK_SSL_WANT_WRITE) {
		if (verbose)
		    DEBUGF(("%d (write) ", cp->fd));
		esock_poll_fd_set_write(ep, cp->fd);
	    }
	    break;
	default:
	    break;
	}
	i++;
	cp = cp->next;
    }
    if (verbose)
	DEBUGF(("\n"));
    return i;
}


static Connection *next_polled_conn(Connection *cp, Connection **cpnext,
				    EsockPoll *ep, int set_wq_fds)
{
    while(cp) {
	if (esock_poll_fd_isset_read(ep, cp->fd) ||
	    (cp->proxy && esock_poll_fd_isset_read(ep, cp->proxy->fd)) ||
	    (esock_poll_fd_isset_write(ep, cp->fd)) ||
	    (cp->proxy && esock_poll_fd_isset_write(ep, cp->proxy->fd))
#ifdef __WIN32__
	    || esock_poll_fd_isset_exception(ep, cp->fd) /* Connect failure in WIN32 */
#endif
	    || (set_wq_fds && (cp->wq.len || 
			       (cp->proxy && cp->proxy->wq.len)))) {
	    *cpnext = cp->next;
	    return cp;
	}
	cp = cp->next;
    }
    *cpnext = NULL;
    return NULL;
}

static void leave_joined_state(Connection *cp)
{
    shutdown(cp->proxy->fd, SHUTDOWN_ALL);
    if (((cp->bp || cp->eof) && cp->clean) ||
	(!cp->bp && !cp->eof)) {
	DEBUGF(("-> SSL_SHUTDOWN\n"));
	cp->state = ESOCK_SSL_SHUTDOWN;
	cp->ssl_want = ESOCK_SSL_WANT_WRITE;
	do_shutdown(cp);
    } else if (cp->close) {
	DEBUGF(("-> (removal)\n"));
	close_and_remove_connection(cp);
    } else {
	DEBUGF(("-> DEFUNCT\n"));
	cp->state = ESOCK_DEFUNCT;
    }
}

/* We are always in state SHUTDOWN here */
static void do_shutdown(Connection *cp)
{
    int ret;

    ret = esock_ssl_shutdown(cp); 
    if (ret < 0) {
	if (sock_errno() == ERRNO_BLOCK) {
	    return;
	} else {
	    /* Something is wrong -- close and remove or move to DEFUNCT */
	    DEBUGF(("Error in SSL shutdown\n"));
	    if (cp->close) {
		DEBUGF(("-> (removal)\n"));
		close_and_remove_connection(cp);
	    } else {
		DEBUGF(("-> DEFUNCT\n"));
		cp->state = ESOCK_DEFUNCT;
	    }
	}
    } else if (ret == 0) {
	/* `close_notify' has been sent. Wait for reception of
           same. */
	return; 
    } else if (ret == 1) {
	/* `close_notify' has been sent, and received. */
	if (cp->close) {
	    DEBUGF(("-> (removal)\n"));
	    close_and_remove_connection(cp);
	} else {
	    DEBUGF(("-> DEFUNCT\n"));
	    cp->state = ESOCK_DEFUNCT;
	}
    }
}

static void close_and_remove_connection(Connection *cp)
{
	safe_close(cp->fd);
	remove_connection(cp);
}

static int reply(int cmd, char *fmt, ...)
{
    static unsigned char replybuf[MAXREPLYBUF];
    unsigned char *buf = replybuf;
    va_list args;
    int len;

    va_start(args, fmt);
    len = put_pars(NULL, fmt, args);
    va_end(args);
    len++;
    if (len > sizeof(replybuf))
	buf = esock_malloc(len);

    PUT_INT8(cmd, buf);
    va_start(args, fmt);
    (void) put_pars(buf + 1, fmt, args);
    va_end(args);
    write_ctrl(buf, len);
    if (buf != replybuf)
	esock_free(buf);
    return len;
}

static int input(char *fmt, ...)
{
    va_list args;
    int len;

    va_start(args, fmt);
    len = get_pars(ebuf + 1, fmt, args);
    va_end(args);
    return len + 1;
}

static int put_pars(unsigned char *buf, char *fmt, va_list args)
{
    char *s, *str, *bin;
    int val, len, pos = 0;

    s = fmt;
    while (*s) {
	switch (*s) {
	case '1':
	    val = va_arg(args, int);
	    if (buf)
		PUT_INT8(val, buf + pos);
	    pos++;
	    break;
	case '2':
	    val = va_arg(args, int);
	    if (buf)
		PUT_INT16(val, buf + pos);
	    pos += 2;
	    break;
	case '4':
	    val = va_arg(args, int);
	    if (buf)
		PUT_INT32(val, buf + pos);
	    pos += 4;
	    break;
	case 's':		/* string */
	    str = va_arg(args, char *);
	    if (buf)
		strcpy((char *)(buf + pos), str);
	    pos += strlen(str) + 1;
	    break;
	case 'b':		/* binary */
	    len = va_arg(args, int);
	    if (buf)
		PUT_INT32(len, buf + pos);
	    pos += 4;
	    bin = va_arg(args, char *);
	    if (buf)
		memcpy(buf + pos, bin, len);
	    pos += len;
	    break;
	default:
	    fprintf(stderr, "esock: Invalid format character: %c\n", *s);
	    exit(EXIT_FAILURE);
	    break;
	}
	s++;
    }
    return pos;
}


static int get_pars(unsigned char *buf, char *fmt, va_list args)
{
    int *ip;
    char *s, **strp, **bin;
    int pos = 0;

    s = fmt;
    while (*s) {
	switch (*s) {
	case '1':
	    ip = va_arg(args, int *);
	    *ip = GET_INT8(buf + pos);
	    pos++;
	    break;
	case '2':
	    ip = va_arg(args, int *);
	    *ip = GET_INT16(buf + pos);
	    pos += 2;
	    break;
	case '4':
	    ip = va_arg(args, int *);
	    *ip = GET_INT32(buf + pos);
	    pos += 4;
	    break;
	case 's':
	    strp = va_arg(args, char **);
	    *strp = (char *)(buf + pos);
	    pos += strlen(*strp) + 1;
	    break;
	case 'b':
	    ip = va_arg(args, int *);
	    *ip = GET_INT32(buf + pos);
	    pos += 4;
	    bin = va_arg(args, char **);
	    *bin = (char *)(buf + pos);
	    pos += *ip;
	    break;
	default:
	    fprintf(stderr, "esock: Invalid format character: %c\n", *s);
	    exit(EXIT_FAILURE);
	    break;
	}
	s++;
    }
    return pos;
}

static FD do_connect(char *lipstring, int lport, char *fipstring, int fport)
{
    struct sockaddr_in sock_addr;
    long inaddr;
    FD fd;
   
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_FD) {
	DEBUGF(("Error calling socket()\n"));
	return fd;
    }
    if (check_num_sock_fds(fd) < 0) 
	return INVALID_FD;
    DEBUGF(("  fd = %d\n", fd));

    /* local */
    if ((inaddr = inet_addr(lipstring)) == INADDR_NONE) {
	DEBUGF(("Error in inet_addr(): lipstring = %s\n", lipstring));
	safe_close(fd);
	sock_set_errno(ERRNO_ADDRNOTAVAIL);
	return INVALID_FD;
    }
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = inaddr;
    sock_addr.sin_port = htons(lport);
    if(bind(fd, (struct sockaddr*) &sock_addr, sizeof(sock_addr)) < 0) {
	DEBUGF(("Error in bind()\n"));
	safe_close(fd);
	/* XXX Set error code for bind error */
	return INVALID_FD;
    }

    /* foreign */
    if ((inaddr = inet_addr(fipstring)) == INADDR_NONE) {
	DEBUGF(("Error in inet_addr(): fipstring = %s\n", fipstring));
	safe_close(fd);
	sock_set_errno(ERRNO_ADDRNOTAVAIL);
	return INVALID_FD;
    }
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = inaddr;
    sock_addr.sin_port = htons(fport);

    SET_NONBLOCKING(fd);

    if(connect(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0) {
	if (sock_errno() != ERRNO_PROGRESS && /* UNIX */
	    sock_errno() != ERRNO_BLOCK) { /* WIN32 */
	    DEBUGF(("Error in connect()\n"));
	    safe_close(fd);
	    return INVALID_FD;
	}
    }
    return fd;
}

static FD do_listen(char *ipstring, int lport, int backlog, int *aport)
{
    static int one = 1;		/* Type must be int, not long */
    struct sockaddr_in sock_addr;
    long inaddr;
    int length;
    FD fd;
    
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_FD) {
	DEBUGF(("Error calling socket()\n"));
	return fd;
    }
    if (check_num_sock_fds(fd) < 0) 
	return INVALID_FD;
    DEBUGF(("  fd = %d\n", fd));
    if ((inaddr = inet_addr(ipstring)) == INADDR_NONE) {
	DEBUGF(("Error in inet_addr(): ipstring = %s\n", ipstring));
	safe_close(fd);
	sock_set_errno(ERRNO_ADDRNOTAVAIL);
	return INVALID_FD;
    }
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = inaddr;
    sock_addr.sin_port = htons(lport);

    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));

    if(bind(fd, (struct sockaddr*) &sock_addr, sizeof(sock_addr)) < 0) {
	DEBUGF(("Error in bind()\n"));
	safe_close(fd);
	return INVALID_FD;
    }
    if (listen(fd, backlog) < 0) {
	DEBUGF(("Error in listen()\n"));
	safe_close(fd);
	return INVALID_FD;
    }
    /* find out assigned local port number */
    length = sizeof(sock_addr);
    if (getsockname(fd, (struct sockaddr *)&sock_addr, &length) < 0) {
	DEBUGF(("Error in getsockname()\n"));
	safe_close(fd);
	return INVALID_FD;
    }
    if (aport)
	*aport = ntohs(sock_addr.sin_port);
    return fd;
}

static FD do_accept(FD listensock, struct sockaddr *saddr, int *len)
{
    FD fd;

    if ((fd = accept(listensock, saddr, len)) == INVALID_FD) {
	DEBUGF(("Error calling accept()\n"));
	return fd;
    }
    if (check_num_sock_fds(fd) < 0) 
	return INVALID_FD;
    return fd;
}

static Connection *new_connection(int state, FD fd)
{
    Connection *cp;
    
    if (!(cp = esock_malloc(sizeof(Connection))))
	return NULL;
    cp->state = state;
    cp->acceptors = 0;
    cp->fd = fd;
    cp->listen_fd = INVALID_FD;
    cp->proxy = NULL;
    cp->opaque = NULL;
    cp->ssl_want = 0;
    cp->eof = 0;
    cp->bp = 0;
    cp->clean = 0;		/* XXX Used? */
    cp->close = 0;
    cp->origin = -1;
    cp->flags = NULL;
    cp->logfp = NULL;
    cp->wq.size = 0;
    cp->wq.buf = NULL;
    cp->wq.len = 0;
    cp->wq.offset = 0;
    cp->next = connections;
    connections = cp;
    return cp;
}


static void print_connections(void)
{
    if(debug) {
    
	Connection *cp = connections;
	DEBUGF(("CONNECTIONS:\n"));
	while (cp) {
	    if (cp->state == ESOCK_JOINED) {
		DEBUGF((" - %s [%8p] (origin = %s)\n"
			"       (fd = %d, eof = %d, wq = %d, bp = %d)\n"
			"       (proxyfd = %d, eof = %d, wq = %d, bp = %d)\n", 
		       connstr[cp->state], cp, originstr[cp->origin],
			cp->fd, cp->eof, cp->wq.len, cp->bp,
			cp->proxy->fd, cp->proxy->eof, cp->proxy->wq.len, 
			cp->proxy->bp));
	    } else if (cp->state == ESOCK_ACTIVE_LISTENING) {
		DEBUGF((" - %s [%8p] (fd = %d, acceptors = %d)\n", 
		       connstr[cp->state], cp, cp->fd, cp->acceptors));
	    } else {
 		DEBUGF((" - %s [%8p] (fd = %d)\n", connstr[cp->state], cp, 
		       cp->fd));
	    }
	    cp= cp->next;
	}
    }
}


static Connection *get_connection(FD fd)
{
    Connection *cp = connections;
    
    while(cp) {
	if(cp->fd == fd)
	    return cp;
	cp = cp->next;
    }
    return NULL;
}

/* 
 * Remove a connection from the list of connection, close the proxy
 * socket and free all resources. The main socket (fd) is *not* 
 * closed here, because the closing of that socket has to be synchronized
 * with the Erlang process controlling this port program.
 */
static void remove_connection(Connection *conn)
{
    Connection **prev = &connections;
    Connection *cp = connections; 
    
    while (cp) {
	if(cp == conn) {
	    DEBUGF(("remove_connection: fd = %d\n", cp->fd));
	    esock_ssl_free(cp);	/* frees cp->opaque only */
	    esock_free(cp->flags);
	    closelog(cp->logfp); /* XXX num_sock_fds */
	    esock_free(cp->wq.buf);
	    if (cp->proxy) {
		safe_close(cp->proxy->fd);
		remove_proxy(cp->proxy);
	    }
	    *prev = cp->next;
	    esock_free(cp);
	    return;
	}
	prev = &cp->next;
	cp = cp->next;
    }
}

static Proxy *get_proxy_by_peerport(int port)
{
    Proxy *p = proxies;

    while(p) {
	if (p->peer_port == port)
	    return p;
	p = p->next;
    }
    return NULL;
}

static Proxy *new_proxy(FD fd)
{
    Proxy *p;

    if (!(p = esock_malloc(sizeof(Proxy))))
	return NULL;

    p->fd = fd;
    p->peer_port = -1;
    p->eof = 0;
    p->bp = 0;
    p->conn = NULL;
    p->wq.size = 0;
    p->wq.buf = NULL;
    p->wq.len = 0;
    p->wq.offset = 0;
    p->next = proxies;
    proxies = p;
    return p;
}

static void remove_proxy(Proxy *proxy)
{
    Proxy *p = proxies, **pp = &proxies;

    while(p) {
	if (p == proxy) {
	    DEBUGF(("remove_proxyfd = %d\n", p->fd));
	    esock_free(p->wq.buf);
	    *pp = p->next;
	    esock_free(p);
	    return;
	}
	pp = &p->next;
	p = p->next;
    }
}

static int check_num_sock_fds(FD fd) 
{
    num_sock_fds++;		/* fd is valid */
#ifdef USE_SELECT
    if (num_sock_fds > FD_SETSIZE) {
	num_sock_fds--;
	sock_set_errno(ERRNO_MFILE);
	safe_close(fd);
	return -1;
    }
#endif
    return 0;
}

static void safe_close(FD fd)
{
    int err;

    err = sock_errno();
    DEBUGF(("safe_close fd = %d\n", fd));
    if (sock_close(fd) < 0) {
	DEBUGF(("safe_close failed\n"));
    } else {
	num_sock_fds--;
    }
    sock_set_errno(err);
}

static void clean_up(void)
{
    Connection *cp, *cpnext;
    Proxy *pp, *ppnext;

    cp = connections;
    while (cp) {
	safe_close(cp->fd);
	cpnext = cp->next;
	remove_connection(cp);
	cp = cpnext;
    }
    
    pp = proxies;
    while (pp) {
	safe_close(pp->fd);
	ppnext = pp->next;
	remove_proxy(pp);
	pp = ppnext;
    }
}

static void ensure_write_queue(WriteQueue *wq, int size)
{
    if (wq->size < size) {
	wq->buf = esock_realloc(wq->buf, size);
	wq->size = size;
    }
}







