aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssl/c_src/esock.c
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/ssl/c_src/esock.c
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/ssl/c_src/esock.c')
-rw-r--r--lib/ssl/c_src/esock.c1904
1 files changed, 1904 insertions, 0 deletions
diff --git a/lib/ssl/c_src/esock.c b/lib/ssl/c_src/esock.c
new file mode 100644
index 0000000000..78d08f7c29
--- /dev/null
+++ b/lib/ssl/c_src/esock.c
@@ -0,0 +1,1904 @@
+/*<copyright>
+ * <year>1999-2008</year>
+ * <holder>Ericsson AB, All Rights Reserved</holder>
+ *</copyright>
+ *<legalnotice>
+ * 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 online 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 AB.
+ *</legalnotice>
+ */
+
+/*
+ * 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 void dump_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",
+ "TRANSPORT_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(("[CLOSE_CMD]: ERROR: fd = %d not found\n", 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 */
+ length = strlen(flags);
+ 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_TRANSPORT_ACCEPT_CMD:
+ /*
+ * ebuf = { op(1), fd(4), flags(N), 0(1)}
+ */
+ input("4s", &fd, &flags);
+ DEBUGF(("[TRANSPORT_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_TRANSPORT_ACCEPT_ERR, "4s", fd, "ebadf");
+ break;
+
+ case ESOCK_SSL_ACCEPT_CMD:
+ input("4s", &fd, &flags);
+ DEBUGF(("[SSL_ACCEPT_CMD] fd = %d, flags = %s\n", fd, flags));
+ cp = get_connection(fd);
+ if (cp)
+ cp->state = ESOCK_SSL_ACCEPT;
+ //reply(ESOCK_SSL_ACCEPT_REP, "4", fd);
+ 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;
+ esock_poll_clear_event(&pollfd, fd);
+ 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;
+
+ case ESOCK_DUMP_STATE_CMD:
+ dump_connections();
+ break;
+
+ case ESOCK_SET_DEBUG_CMD:
+ /*
+ * ebuf = {cmd(1), debug(1)}
+ */
+ input("1", &debug);
+ break;
+
+ case ESOCK_SET_DEBUGMSG_CMD:
+ /*
+ * ebuf = {cmd(1), debugmsg(1)}
+ */
+ input("1", &debugmsg);
+ 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_TRANSPORT_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_TRANSPORT_ACCEPT, msgsock);
+ newcp->origin = ORIG_ACCEPT;
+ reply(ESOCK_TRANSPORT_ACCEPT_REP, "44", cp->fd, 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) {
+ cp->errstr = ssl_errstr();
+ 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 (cp->errstr != NULL) { /* this means we got an error in ssl_accept_init */
+ /* N.B.: The *listen fd* is reported. */
+ reply(ESOCK_SSL_ACCEPT_ERR, "4s", msgsock, cp->errstr);
+ close_and_remove_connection(cp);
+ break;
+ }
+ if (esock_ssl_accept(cp) < 0) {
+ if (sock_errno() != ERRNO_BLOCK) {
+ /* Handshake failed. */
+ reply(ESOCK_SSL_ACCEPT_ERR, "4s", msgsock,
+ ssl_errstr());
+ DEBUGF(("ERROR: handshake: %s\n", ssl_errstr()));
+ close_and_remove_connection(cp);
+ }
+ } else {
+ /* SSL handshake successful: publish */
+ reply(ESOCK_SSL_ACCEPT_REP, "4", 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 %d\n", errno));
+ 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)))
+ || cp->errstr != NULL) {
+ *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;
+ cp->errstr = NULL;
+ 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 void dump_connections(void)
+{
+ Connection *cp = connections;
+ Proxy *pp = proxies;
+ time_t t = time(NULL);
+ int length = 0;
+ struct sockaddr_in iserv_addr;
+
+ __debugprintf("CONNECTIONS %s", ctime(&t));
+ while (cp) {
+ if (cp->state == ESOCK_JOINED) {
+ __debugprintf(" - %s [%8p] (origin = %s)\n"
+ " (fd = %d, eof = %d, wq = %d, bp = %d), close = %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->close,
+ cp->proxy->fd, cp->proxy->eof, cp->proxy->wq.len,
+ cp->proxy->bp);
+ } else if (cp->state == ESOCK_ACTIVE_LISTENING) {
+ __debugprintf(" - %s [%8p] (fd = %d, acceptors = %d)\n",
+ connstr[cp->state], cp, cp->fd, cp->acceptors);
+ } else {
+ __debugprintf(" - %s [%8p] (fd = %d)\n", connstr[cp->state], cp,
+ cp->fd);
+ }
+ length = sizeof(iserv_addr);
+ if ((cp->state == ESOCK_ACTIVE_LISTENING) ||
+ (cp->state == ESOCK_PASSIVE_LISTENING)) {
+ getsockname(cp->fd, (struct sockaddr *) &iserv_addr, &length);
+ __debugprintf(" (ip = %s, port = %d)\n",
+ inet_ntoa(iserv_addr.sin_addr),
+ ntohs(iserv_addr.sin_port));
+ }
+ else {
+ getsockname(cp->fd, (struct sockaddr *) &iserv_addr, &length);
+ __debugprintf(" (local_ip = %s, local_port = %d)\n",
+ inet_ntoa(iserv_addr.sin_addr),
+ ntohs(iserv_addr.sin_port));
+ length = sizeof(iserv_addr);
+ getpeername(cp->fd, (struct sockaddr *) &iserv_addr, &length);
+ __debugprintf(" (remote_ip = %s, remote_port = %d)\n",
+ inet_ntoa(iserv_addr.sin_addr),
+ ntohs(iserv_addr.sin_port));
+ }
+ cp=cp->next;
+ }
+
+ __debugprintf("PROXIES\n");
+ while (pp) {
+ __debugprintf(" - fd = %d [%8p] (external_fd = %d, peer_port = %d,"
+ " eof = %d)\n", pp->fd, pp, pp->conn->fd, pp->peer_port,
+ pp->eof);
+
+ pp= pp->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;
+ }
+}
+
+
+
+
+
+
+