/* ==========================================================================
 * libevnet/src/socket.c - Network server library for libevent.
 * --------------------------------------------------------------------------
 * Copyright (c) 2006  William Ahern
 * Copyright (c) 2006  Barracuda Networks, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ==========================================================================
 */
#include <stdio.h>	/* snprintf(3) */
#include <stdlib.h>	/* malloc(3) free(3) */
#include <stddef.h>	/* offsetof */

#include <errno.h>	/* EINVAL ESOCKNTNOSUPPORT EINPROGRESS EINTR
			 * EAGAIN ECONNABORTED */
#include <string.h>	/* strerror(3) memcpy(3) strncpy(3) */

#include <assert.h>	/* assert(3) */

#include <windows.h>	/* GetLastError SetLastError */

#include <fcntl.h>	/* O_NONBLOCK F_SETFL F_GETFL fcntl(2) */

#include <sys/queue.h>	/* SLIST LIST */
#include <sys/param.h>	/* MIN */

#include <sys/types.h>	/* socklen_t mode_t htons(3) ntohs(3) */

#include <sys/time.h>	/* struct timeval timerclear gettimeofday(2) */

#include <sys/stat.h>	/* umask(2) */

#if !_WIN32
#include <sys/socket.h>	/* SOMAXCONN AF_UNIX AF_INET AF_INET6 SOCK_STREAM
			 * PF_UNIX PF_INET PF_INET6 SOL_SOCKET SO_ERROR
			 * struct sockaddr socket(2) connect(2) getsockopt(2) */

#include <netinet/in.h>	/* struct sockaddr_in struct sockaddr_in6 */
#include <netinet/tcp.h>/* IPPROTO_TCP TCP_NODELAY */
#include <sys/un.h>	/* struct sockaddr_un */

#include <unistd.h>	/* close(2) */

#include <netdb.h>	/* struct addrinfo struct servent getaddrinfo(3)
			 * getservbyname(3) */
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include <winsock2.h>	/* INVALID_SOCKET closesocket() */

#include <event.h>	/* EV_READ EV_WRITE EV_PERSIST EV_TIMEOUT
			 * struct event_base event_set(3) event_set_base(3)
			 * event_add(3) */

#include <arena/proto.h>
#include <arena/util.h>

#include "socket.h"
#include "lookup.h"
#include "tls.h"
#include "bufio.h"
#include "bufio/socket.h"


#ifndef MARK
#define MARK	(fprintf(stderr, "@%s:%d\n", __FUNCTION__, __LINE__))
#endif


struct socket;
struct socket_accept;
struct socket_connect;


const char *socket_errlist[] = {
	[SOCKET_ESUCCESS]	= "Success",
	[SOCKET_ESYSTEM]	= "System error",
	[SOCKET_ETIMEDOUT]	= "Operation timed out",
	[SOCKET_ECANCELLED]	= "Operation cancelled",
	[SOCKET_ENOTFOUND]	= "Hostname not found",
	[SOCKET_ENOTLS]		= "Unable to negotiate TLS/SSL",
	[SOCKET_ENOTCONNECTED]	= "Socket not connected",
}; /* socket_errlist[] */

const int socket_nerr	= sizeof socket_errlist / sizeof *socket_errlist;


const struct socket_options socket_defaults = {
	.sun_mask	= 0000,
	.sun_unlink	= 0,
	.sa_reuseaddr	= 0,
	.so_type	= SOCK_STREAM,
	.so_nonblock	= 1,
	.so_backlog	= SOMAXCONN,
	.sin_resolv	= 0,
	.sin_resolv	= 0,
}; /* socket_defaults */


#define SOCKET_FRAME_PUSH(p, f)	do {					\
	(f)->xp	= &(p);							\
	SLIST_INSERT_HEAD(&(p)->frames, (f), sle);			\
} while(0)

#define SOCKET_FRAME_OKAY(f)	(*(f)->xp != 0)

#define SOCKET_FRAME_POP(p, f)	do {					\
	if (SOCKET_FRAME_OKAY((f))) {					\
		assert((f) == SLIST_FIRST(&(p)->frames));		\
		SLIST_REMOVE_HEAD(&(p)->frames, sle);			\
	}								\
} while(0)

#define SOCKET_FRAME_KILL(f)	(*(f)->xp = 0)


struct socket_frame {
	struct socket **xp;

	SLIST_ENTRY(socket_frame) sle;
}; /* struct socket_frame */


struct socket_accept_frame {
	struct socket_accept **xp;

	SLIST_ENTRY(socket_accept_frame) sle;
}; /* struct socket_accept_frame */


struct socket_connect_frame {
	struct socket_connect **xp;

	SLIST_ENTRY(socket_connect_frame) sle;
}; /* struct socket_connect_frame */


enum socket_state {
	SOCKET_STATE_QUIET,
	SOCKET_STATE_LOOKUP,
	SOCKET_STATE_POLLING,
	SOCKET_STATE_RLOOKUP,
	SOCKET_STATE_START_TLS,
	SOCKET_STATE_CONNECTED,
}; /* enum socket_state */


struct socket_endpoint {
	struct sockaddr_storage ss;
	char *hostname;
}; /* struct socket_endpoint */


struct socket_accept {
	struct socket *socket;

	SOCKET fd;

	enum socket_state state;

	struct socket_endpoint local, peer;

	struct {
		void (*fn)(struct socket *, struct socket *, enum socket_errno, void *);
		void *arg;
	} cb;

	struct {
		struct event event;
		int pending;
	} ev;

	struct tls *tls;

	struct timeval expire;

	LIST_ENTRY(socket_accept) le;

	SLIST_HEAD(, socket_accept_frame) frames;
}; /* struct socket_accept */


struct socket_connect {
	struct socket *socket;

	SOCKET fd;

	enum socket_state state;

	struct {
		struct sockaddr_storage *buf;
		struct sockaddr_storage *pos;
		struct sockaddr_storage *end;
	} ss;

	struct socket_endpoint local, peer;

	struct {
		void (*fn)(struct socket *, enum socket_errno, void *);
		void *arg;
	} cb;

	struct {
		struct event event;
		int pending;
	} ev;

	struct tls *tls;

	struct timeval expire;

	LIST_ENTRY(socket_connect) le;

	SLIST_HEAD(, socket_connect_frame) frames;
}; /* struct socket_connect */


struct socket {
	SOCKET fd;

	enum socket_mode mode;

	int state;

	enum socket_errno so_errno;

	struct socket_options opts;

	struct event_base *base;
	const struct arena_prototype *ap;
	struct lookup *lu;

	SLIST_HEAD(, socket_frame) frames;

	LIST_HEAD(, socket_accept) accept;

	LIST_HEAD(, socket_connect) connect;

	struct {
		struct tls_identity *identity;
		struct tls *session;

		void (*cb)(struct socket *, enum socket_errno, void *);
		void *arg;
	} tls;

	struct socket_name name;

	struct socket_endpoint local, peer;

	struct bufio_socket iobuf;

	LIST_ENTRY(socket) le;
}; /* struct socket */


static int so_set_nonblock(SOCKET fd) {
#ifdef O_NONBLOCK
	int flags;

	if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
		return -1;

	if (0 != fcntl(fd, F_SETFL, flags | O_NONBLOCK))
		return -1;
#elif defined FIONBIO
	unsigned long nonblock	= 1;

	if (0 != ioctlsocket(fd, FIONBIO, &nonblock))
		return -1;
#else
#error Do not know how to enable non-blocking I/O
#endif
	return 0;
} /* so_set_nonblock() */


static SOCKET so_open(struct sockaddr_storage *ss, int so_type) {
	SOCKET sd	= INVALID_SOCKET;

	switch (ss->ss_family) {
	case AF_INET:
		sd	= socket(PF_INET, so_type, 0);

		break;
#ifdef USE_IPV6
	case AF_INET6:
		sd	= socket(PF_INET6, so_type, 0);

		break;
#endif
#ifndef WIN32
	case AF_UNIX:
		sd	= socket(PF_UNIX, so_type, 0);

		break;
#endif
	default:
#if _WIN32
		SetLastError(EINVAL);
#else
		SetLastError(ESOCKTNOSUPPORT);
#endif
	} /* switch (ss_family) */

	return sd;
} /* so_open() */


static struct socket_connect *socket_connect_open(struct socket *s, enum socket_errno *so_errno) {
	static const struct socket_connect s_initializer;
	struct socket_connect *c	= 0;
	int sys_errno;

	if (!(c = s->ap->malloc(s->ap, sizeof *c, 0))) {
		*so_errno	= SOCKET_ESYSTEM;

		return 0;
	}

	*c		= s_initializer;
	c->fd		= -1;
	c->socket	= s;

	return c;
} /* socket_connect_open() */


static void socket_connect_close(struct socket *s, struct socket_connect *c, int notify) {
	socket_connect_cb cb;
	void *arg;

	assert(c->state != SOCKET_STATE_LOOKUP && c->state != SOCKET_STATE_RLOOKUP);

	if (c->ev.pending)
		(void)event_del(&c->ev.event), c->ev.pending = 0;

	if (c->fd != INVALID_SOCKET)
		(void)closesocket(c->fd), c->fd = INVALID_SOCKET;

	tls_close(c->tls), c->tls = 0;

	s->ap->free(s->ap, c->ss.buf), c->ss.buf = 0;

	cb	= c->cb.fn;
	arg	= c->cb.arg;

	s->ap->free(s->ap, c);

	if (notify && cb != 0)
		cb(s, s->so_errno = SOCKET_ECANCELLED, arg);

	return /* void */;
} /* socket_connect_close() */


static struct socket_accept *socket_accept_open(struct socket *s, enum socket_errno *so_errno) {
	static const struct socket_accept a_initializer;
	struct socket_accept *a	= 0;
	int sys_errno;

	if (!(a = s->ap->malloc(s->ap, sizeof *a, 0))) {
		*so_errno	= SOCKET_ESYSTEM;

		return 0;
	}

	*a		= a_initializer;
	a->fd		= -1;
	a->socket	= s;

	return a;
} /* socket_accept_open() */


static void socket_accept_close(struct socket *s, struct socket_accept *a, int notify) {
	socket_accept_cb cb	= a->cb.fn;
	void *arg		= a->cb.arg;

	assert(a->state != SOCKET_STATE_LOOKUP);

	if (a->ev.pending)
		(void)event_del(&a->ev.event), a->ev.pending = 0;

	if (a->fd != INVALID_SOCKET)
		(void)closesocket(a->fd), a->fd = INVALID_SOCKET;

	tls_close(a->tls), a->tls = 0;

	s->ap->free(s->ap, a->peer.hostname);
	s->ap->free(s->ap, a->local.hostname);

	s->ap->free(s->ap, a);

	if (notify && cb != 0)
		cb(s, 0, s->so_errno = SOCKET_ECANCELLED, arg);

	return /* void */;
} /* socket_accept_close() */


struct socket *socket_open(struct socket_name *name, const struct socket_options *opts, struct event_base *base, const struct arena_prototype *ap, enum socket_errno *so_errno) {
	static const struct socket s_initializer;
	struct socket *s	= 0;
	enum bufio_errno io_errno;
	int sys_errno;

	assert(so_errno != 0);
	assert(name != 0);

	if (!opts)
		opts	= &socket_defaults;

	if (!ap)
		ap	= ARENA_STDLIB;

	if (!(s = ap->malloc(ap, sizeof *s, 0)))
		goto sysfail;

	*s		= s_initializer;
	s->fd		= -1;
	s->opts		= *opts;
	s->base		= base;
	s->ap		= ap;
	s->name		= *name;

	if (0 == bufio_socket_init(&s->iobuf, BUFIO_SOCKET_FD_WAIT, &bufio_socket_defaults, base, ap, &io_errno))
		goto sysfail;

	if (name->type == &name->addr)
		s->name.type	= &s->name.addr;
	else if (name->type == &name->host)
		s->name.type	= &s->name.host;
#if !_WIN32
	else if (name->type == &name->local)
		s->name.type	= &s->name.local;
#endif
	else
		goto sysfail;

	SLIST_INIT(&s->frames);

	return s;
sysfail:
	*so_errno	= SOCKET_ESYSTEM;
anyfail:
	sys_errno	= GetLastError();

	ap->free(ap, s);

	SetLastError(sys_errno);

	return 0;
} /* socket_open() */


static struct socket *socket_clone(struct socket *s, enum socket_errno *so_errno) {
	struct socket *s1;

	if (0 == (s1 = socket_open(&s->name, &s->opts, s->base, s->ap, so_errno)))
		return 0;

	s1->mode		= s->mode;
	s1->tls.identity	= s->tls.identity;

	return s1;
} /* socket_clone() */


void socket_cancel(struct socket *s, int notify) {
	struct socket_frame f;
	struct socket_accept *a;
	struct socket_connect *c;

	SOCKET_FRAME_PUSH(s, &f);

	while (SOCKET_FRAME_OKAY(&f) && LIST_END(&s->accept) != (a = LIST_FIRST(&s->accept))) {
		struct socket_accept_frame *fp;

		LIST_REMOVE(a, le);

		SLIST_FOREACH(fp, &a->frames, sle)
			SOCKET_FRAME_KILL(fp);

		SLIST_INIT(&a->frames);

		socket_accept_close(s, a, notify);
	}

	while (SOCKET_FRAME_OKAY(&f) && LIST_END(&s->connect) != (c = LIST_FIRST(&s->connect))) {
		struct socket_connect_frame *fp;

		LIST_REMOVE(c, le);

		SLIST_FOREACH(fp, &c->frames, sle)
			SOCKET_FRAME_KILL(fp);

		SLIST_INIT(&c->frames);

		socket_connect_close(s, c, notify);
	}

	SOCKET_FRAME_POP(s, &f);

	return /* void */;
} /* socket_cancel() */


void socket_close(struct socket *s) {
	struct socket_frame f, *fp;

	if (!s)
		return /* void */;

	SOCKET_FRAME_PUSH(s, &f);

	socket_cancel(s, 0);

	if (!SOCKET_FRAME_OKAY(&f))
		return /* void */;

	SOCKET_FRAME_POP(s, &f);

	/*
	 * Kill all previous frames. When we return all their object
	 * references will become invalid.
	 */
	SLIST_FOREACH(fp, &s->frames, sle)
		SOCKET_FRAME_KILL(fp);

	SLIST_INIT(&s->frames);

	tls_close(s->tls.session), s->tls.session = 0;
	lookup_close(s->lu), s->lu = 0;

	if (bufio_socket_initialized(&s->iobuf))
		bufio_socket_destroy(&s->iobuf);

	if (s->fd != INVALID_SOCKET)
		(void)closesocket(s->fd), s->fd = INVALID_SOCKET;

	s->ap->free(s->ap, s->peer.hostname);
	s->ap->free(s->ap, s->local.hostname);

	s->ap->free(s->ap, s);

	return /* void */;
} /* socket_close() */


void socket_enable_tls(struct socket *s, struct tls_identity *tlsid) {
	assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));

	s->tls.identity	= tlsid;

	return /* void */;
} /* socket_enable_tls() */


void socket_getsockinfo(struct socket *s, SOCKET *fd, struct tls **tls) {
	if (fd != 0)
		*fd	= s->fd;

	if (tls != 0)
		*tls	= s->tls.session;

	return /* void */;
} /* socket_getsockinfo() */


void socket_discard(struct socket *s, SOCKET *fd, struct tls **tls) {
	if (fd != 0) {
		*fd	= s->fd;
		s->fd	= -1;
	}

	if (tls != 0) {
		*tls		= s->tls.session;
		s->tls.session	= 0;
	}

	socket_close(s);

	return /* void */;
} /* socket_discard() */


enum socket_errno socket_getpeername(struct socket *s, struct sockaddr *sa, socklen_t *salen, const char **host) {
	if (s->state != SOCKET_STATE_CONNECTED && s->state != SOCKET_STATE_START_TLS)
		return SOCKET_ENOTCONNECTED;

	if (sa != 0) {
		assert(salen != 0);

		*salen	= MIN(*salen, SOCKET_SA_LENOF(&s->peer.ss));

		(void)memcpy(sa, &s->peer.ss, *salen);
	}

	if (host != 0)
		*host	= s->peer.hostname;

	return 0;
} /* socket_getpeername() */


enum socket_errno socket_getsockname(struct socket *s, struct sockaddr *sa, socklen_t *salen, const char **host) {
	if (s->state != SOCKET_STATE_CONNECTED && s->state != SOCKET_STATE_START_TLS)
		return SOCKET_ENOTCONNECTED;

	if (sa != 0) {
		assert(salen != 0);

		*salen	= MIN(*salen, SOCKET_SA_LENOF(&s->local.ss));

		(void)memcpy(sa, &s->local.ss, *salen);
	}

	if (host != 0)
		*host	= s->local.hostname;

	return 0;
} /* socket_getsockname() */


enum socket_errno socket_setsockstate(struct socket *s, SOCKET fd, enum socket_mode mode) {
	enum bufio_errno io_errno;
	socklen_t salen;

	s->fd	= fd;

	if (0 == bufio_socket_init(&s->iobuf, s->fd, 0, s->base, s->ap, &io_errno))
		goto sysfail;

	s->mode			= mode;
	s->state		= SOCKET_STATE_CONNECTED;

	(void)getsockname(fd, (struct sockaddr *)&s->local.ss, (salen = sizeof s->local.ss, &salen));
	(void)getpeername(fd, (struct sockaddr *)&s->peer.ss, (salen = sizeof s->peer.ss, &salen));

	return 0;
sysfail:
	s->fd	= -1;

	return SOCKET_ESYSTEM;
} /* socket_setsockstate() */


struct bufio_source *socket_to_source(struct socket *s) {
	assert(bufio_socket_initialized(&s->iobuf));

	return bufio_socket_to_source(&s->iobuf);
} /* socket_to_source() */


struct bufio_sink *socket_to_sink(struct socket *s) {
	assert(bufio_socket_initialized(&s->iobuf));

	return bufio_socket_to_sink(&s->iobuf);
} /* socket_to_sink() */


static void socket_accept_throw(struct socket *s, struct socket_accept *a, enum socket_errno so_errno) {
	struct socket *s1	= 0;
	socket_accept_cb cb	= a->cb.fn;
	void *arg		= a->cb.arg;
	enum bufio_errno bio_errno;
	int sys_errno;

	if (so_errno != 0)
		goto anyfail;

	if (0 == (s1 = socket_clone(s, &so_errno)))
		goto anyfail;

	if (0 == bufio_socket_init(&s1->iobuf, a->fd, 0, s1->base, s1->ap, &bio_errno))
		goto sysfail;

	if (a->tls)
		bufio_socket_switch_tls(&s1->iobuf, a->tls);

	s1->state		= SOCKET_STATE_CONNECTED;
	s1->tls.session		= a->tls;
	a->tls			= 0;

	s1->peer		= a->peer;
	a->peer.hostname	= 0;

	s1->local		= a->local;
	a->local.hostname	= 0;

	s1->fd			= a->fd;
	a->fd			= -1;

	LIST_REMOVE(a, le);

	socket_accept_close(s, a, 0);

	cb(s, s1, s->so_errno = 0, arg);

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	sys_errno	= GetLastError();

	socket_close(s1);

	LIST_REMOVE(a, le);

	socket_accept_close(s, a, 0);

	SetLastError(sys_errno);

	cb(s, 0, s->so_errno = so_errno, arg);

	return /* void */;
} /* socket_accept_throw() */


static void socket_accept_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
	struct socket_accept *a	= arg;
	struct socket *s	= a->socket;

	a->state	= 0;

	socket_accept_throw(s, a, (tls_errno == 0)? 0 : SOCKET_ENOTLS);

	return /* void */;
} /* socket_accept_catch_tls() */


static void socket_accept_start_tls(struct socket *s, struct socket_accept *a) {
	enum tls_errno tls_errno;

	a->state	= 0;

	if (!s->tls.identity) {
		socket_accept_throw(s, a, 0);

		return /* void */;
	}

	/* FIXME: Bubble up TLS and OpenSSL errors. */
	if (!(a->tls = tls_open(a->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
		socket_accept_throw(s, a, SOCKET_ENOTLS);

		return /* void */;
	}

	/* FIXME: Add timeout capability. */
	tls_accept(a->tls, &socket_accept_catch_tls, a, 0);

	return /* void */;
} /* socket_accept_start_tls() */


static void socket_accept_rresolved(int numans, struct lookup_rr *ans, int numadd, struct lookup_rr *add, enum lookup_errno lu_errnp, void *arg) {
	struct socket_accept *a	= arg;
	struct socket *s	= a->socket;

	a->state	= 0;

	if (numans > 0 && ans->type == LOOKUP_IN_PTR) {
		if ((a->peer.hostname = s->ap->malloc(s->ap, ans->rr.ptr.hostlen + 1, 1)))
			(void)snprintf(a->peer.hostname, ans->rr.ptr.hostlen + 1, "%.*s", (int)ans->rr.ptr.hostlen, ans->rr.ptr.host);
	}

	socket_accept_start_tls(s, a);

	return /* void */;
} /* socket_accept_rresolved() */


static void socket_accept_rlookup(struct socket *s, struct socket_accept *a) {
#ifdef USE_CARES
	if (!s->opts.sin_resolv) {
		socket_accept_start_tls(s, a);

		return /* void */;
	}

	if (s->lu == 0 && !(s->lu = lookup_open(0, s->base, s->ap))) {
		socket_accept_throw(s, a, SOCKET_ESYSTEM);

		return /* void */;
	}

	a->state	= SOCKET_STATE_RLOOKUP;

	/* FIXME: Suport timeout. */
	lookup_ptr(s->lu, (struct sockaddr *)&a->peer.ss, SOCKET_SA_LENOF(&a->peer.ss), 0, &socket_accept_rresolved, a, 0);

	return /* void */;
#else
	socket_accept_start_tls(s, a);

	return /* void */;
#endif /* USE_CARES */
} /* socket_accept_rlookup() */


static void socket_accept_wake(int, short, void *);

static enum socket_errno socket_accept_poll(struct socket *s, struct socket_accept *a) {
	struct timeval now, timeout, expire;

	/* Short circuit if we're already pending without a timeout. */
	if (a->ev.pending && !timerisset(&a->expire))
		return 0;

	if (timerisset(&a->expire)) {
		assert(0 == gettimeofday(&now, 0));

		if (timercmp(&now, &a->expire, <)) {
			timersub(&a->expire, &now, &timeout);
		} else { /* Hmmm, we snuck past the deadline.... */
			timerclear(&timeout);
			timeout.tv_usec	= 1;
		}
	} else
		timerclear(&timeout);

	if (a->ev.pending)
		(void)event_del(&a->ev.event), a->ev.pending = 0;

	event_set(&a->ev.event, a->fd, EV_READ | EV_PERSIST, socket_accept_wake, a);

	if (s->base)
		event_base_set(s->base, &a->ev.event);

	if (0 != event_add(&a->ev.event, timerisset(&timeout)? &timeout : 0))
		return SOCKET_ESYSTEM;

	a->ev.pending	= 1;

	return 0;
} /* socket_accept_poll() */


static void socket_accept_wake(SOCKET fd, short events, void *arg) {
	struct { struct socket_frame s; struct socket_accept_frame a; } f;
	struct socket_accept *a	= arg;
	struct socket *s	= a->socket;
	socket_accept_cb cb	= a->cb.fn;
	struct socket_accept *a1;
	enum socket_errno so_errno;
	int sys_errno;
	struct sockaddr_storage ss;
	socklen_t salen;
	int i;

	/* Stick to the a->fd reference so we don't get confused later. */
	fd	= INVALID_SOCKET;
	arg	= a->cb.arg;

	SOCKET_FRAME_PUSH(s, &f.s);
	SOCKET_FRAME_PUSH(a, &f.a);

	if (events & EV_TIMEOUT) {
		so_errno	= SOCKET_ETIMEDOUT;

		goto anyfail;
	}

	for (i = 0; i < 3 && SOCKET_FRAME_OKAY(&f.a); i++) {
		salen	= sizeof ss;

		if (INVALID_SOCKET == (fd = accept(a->fd, (struct sockaddr *)&ss, &salen))) {
			switch (GetLastError()) {
			case EINTR:
				/* FALL THROUGH */
			case EAGAIN:
				/* FALL THROUGH */
#ifdef ECONNABORTED
			case ECONNABORTED:
				/* FALL THROUGH */
#endif
#ifdef ECONNRESET
			case ECONNRESET:
				/* FALL THROUGH */
#endif
#ifdef WSAEWOULDBLOCK
			case WSAEWOULDBLOCK:
				/* FALL THROUGH */
#endif

				if (0 != (so_errno = socket_accept_poll(s, a)))
					goto anyfail;

				goto endcall;
			default:
				goto sysfail;
			}
		}

		if (s->opts.so_nodelay
		&&  0 != setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&s->opts.so_nodelay, sizeof s->opts.so_nodelay))
			goto sysfail;

		if (s->opts.so_nonblock && 0 != so_set_nonblock(fd))
			goto sysfail;

		/* If there was an expiration, this was a oneshot deal. */
		if (timerisset(&a->expire))
			socket_cancel(s, 0);

		assert(SOCKET_FRAME_OKAY(&f.s));

		/*
		 * Fork this accept so the new one can finish up connection
		 * establishment and callback
		 */
		if (0 == (a1 = socket_accept_open(s, &so_errno)))
			goto anyfail;

		a1->fd		= fd;
		fd		= -1;
		a1->cb		= a->cb;
		a1->local	= a->local;
		a1->peer.ss	= ss;

		if (a->local.hostname != 0
		&&  0 == (a1->local.hostname = ap_strdup(a1->socket->ap, a->local.hostname)))
			goto anyfail;

		if (0 != getsockname(a1->fd, (struct sockaddr *)&a1->local.ss, (salen = sizeof a1->local.ss, &salen)))
			goto anyfail;

		LIST_INSERT_HEAD(&s->accept, a1, le);

		/* Out of our hands, now. */
		socket_accept_rlookup(s, a1);
	} /* while (i++ < 3) */

endcall:
	SOCKET_FRAME_POP(a, &f.a);
	SOCKET_FRAME_POP(s, &f.s);

	return /* void */;	
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	assert(SOCKET_FRAME_OKAY(&f.s));

	sys_errno	= GetLastError();

	if (fd != INVALID_SOCKET)
		(void)closesocket(fd);

	socket_cancel(s, 0);

	SetLastError(sys_errno);

	cb(s, 0, s->so_errno = so_errno, arg);

	SOCKET_FRAME_POP(a, &f.a);
	SOCKET_FRAME_POP(s, &f.s);

	return /* void */;
} /* socket_accept_wake() */


static enum socket_errno socket_accept_bind(struct socket *s, struct socket_accept *a, struct sockaddr_storage *ss) {
	mode_t omask;
	int sys_errno;

	(void)memcpy(&a->local.ss, ss, sizeof a->local.ss);

	if (INVALID_SOCKET == (a->fd = so_open(ss, s->opts.so_type)))
		return SOCKET_ESYSTEM;

	if (s->opts.sa_reuseaddr
	&&  0 != setsockopt(a->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&s->opts.sa_reuseaddr, sizeof s->opts.sa_reuseaddr))
		return SOCKET_ESYSTEM;

	if (0 != so_set_nonblock(a->fd))
		return SOCKET_ESYSTEM;

#if !_WIN32
	if (ss->ss_family == AF_UNIX && s->opts.sun_unlink) {
		char path[sizeof (((struct sockaddr_un *)ss)->sun_path) + 1];

		path[sizeof path - 1]	= '\0';

		/* Should we fail if errno != ENOENT? */
		(void)unlink(strncpy(path, ((struct sockaddr_un *)ss)->sun_path, sizeof path - 1));
	}
#endif

	omask	= umask(s->opts.sun_mask);

	if (0 != bind(a->fd, (struct sockaddr *)ss, SOCKET_SA_LENOF(ss))) {
		sys_errno	= GetLastError();

		(void)umask(omask);

		SetLastError(sys_errno);

		return SOCKET_ESYSTEM;
	}

	(void)umask(omask);

	if (0 != listen(a->fd, s->opts.so_backlog))
		return SOCKET_ESYSTEM;

	return 0;
} /* socket_accept_bind() */


/*
 * Safe. Never enters a callback and so never alters our stack state.
 * Caller is responsible for error handling.
 */
static enum socket_errno socket_accept_all(struct socket *s, unsigned num, struct sockaddr_storage *ss) {
	struct socket_accept *a0, *a;
	struct servent *ent;
	long port;
	char *end;
	enum socket_errno so_errno;
	unsigned i;

	assert(0 != (a0 = LIST_FIRST(&s->accept)));

	assert(num > 0 && ss != 0);

	if (s->name.type == &s->name.host) {
		port	= strtol(s->name.host.port, &end, 10);

		if (port < 0 || port > 65535 || *end != '\0') {
			if (!(ent = getservbyname(s->name.host.port, (s->opts.so_type == SOCK_DGRAM)? "udp" : "tcp")))
				return SOCKET_ESYSTEM;

			port	= ntohs(ent->s_port);
		}

		for (i = 0; i < num; i++) {
			switch (ss[i].ss_family) {
			case AF_INET:
				((struct sockaddr_in *)&ss[i])->sin_port	= htons(port);

				break;
#if USE_IPV6
			case AF_INET6:
				((struct sockaddr_in6 *)&ss[i])->sin6_port	= htons(port);

				break;
#endif
			default:
				return SOCKET_ESYSTEM;
			}
		}
	} /* if (s->name.type == &s->name.host) */

	for (i = 0;;) {
		assert(a = LIST_FIRST(&s->accept));

		a->cb	= a0->cb;

		if (0 != (so_errno = socket_accept_bind(s, a, &ss[i])))
			return so_errno;

		if (0 != (so_errno = socket_accept_poll(s, a)))
			return so_errno;

		a->state	== SOCKET_STATE_POLLING;

		if (++i >= num)
			break;

		if (0 == (a = socket_accept_open(s, &so_errno)))
			return so_errno;

		LIST_INSERT_HEAD(&s->accept, a, le);
	}

	return 0;
} /* socket_accept_all() */


static void socket_accept_resolved(int numans, struct lookup_rr *ans, int numadd, struct lookup_rr *add, enum lookup_errno lu_errno, void *arg) {
	struct socket *s		= arg;
	struct sockaddr_storage *ss	= 0;
	struct socket_accept *a;
	socket_accept_cb cb;
	void *p;
	unsigned i;
	struct lookup_rr *rr;
	int so_errno, sys_errno;

	assert((a = LIST_FIRST(&s->accept)));

	a->state	= 0;

	cb	= a->cb.fn;
	arg	= a->cb.arg;

	if (lu_errno == LOOKUP_ETIMEDOUT) {
		so_errno	= SOCKET_ETIMEDOUT;

		goto anyfail;
	} else if (LOOKUP_FAILURE(lu_errno)) {
		so_errno	= SOCKET_ENOTFOUND;

		goto anyfail;
	}

	for (i = 0, rr = ans; rr != 0; rr = rr->next) {
		if (rr->type != LOOKUP_IN_A && rr->type != LOOKUP_IN_AAAA)
			continue;

		if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
			goto sysfail;

		ss	= p;

		(void)memcpy(&ss[i++], &rr->rr.ip.sa, rr->rr.ip.salen);
	}

	if (i == 0) {
		so_errno	= SOCKET_ENOTFOUND;

		goto anyfail;
	}

	if (0 != (so_errno = socket_accept_all(s, i, ss)))
		goto anyfail;

	s->ap->free(s->ap, ss);

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	sys_errno	= GetLastError();

	socket_cancel(s, 0);

	s->ap->free(s->ap, ss);

	SetLastError(sys_errno);

	cb(s, 0, s->so_errno = so_errno, arg);

	return /* void */;
} /* socket_accept_resolved() */


void socket_accept(struct socket *s, socket_accept_cb cb, void *arg, struct timeval *timeout) {
	struct socket_accept *a			= 0;
	struct sockaddr_storage *ss		= 0;
#if USE_IPV6
	struct addrinfo hints, *res, *res0	= 0;
#else
	struct hostent *ent;
#endif
	struct timeval now;
	void *p;
	unsigned i;
	enum socket_errno so_errno;
	int sys_errno;

	assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));

	s->mode	= SOCKET_MODE_SERVER;

	if (!(a = socket_accept_open(s, &so_errno)))
		goto anyfail;

	LIST_INSERT_HEAD(&s->accept, a, le);

	a->cb.fn	= cb;
	a->cb.arg	= arg;

	if (timeout != 0 && timerisset(timeout)) {
		assert(0 == gettimeofday(&now, 0));

		timeradd(&now, timeout, &a->expire);
	} else
		timerclear(&a->expire);

	if (s->name.type == &s->name.host) {
#if USE_IPV6
		(void)memset(&hints, 0, sizeof hints);

#ifdef USE_CARES
		hints.ai_flags		= AI_NUMERICHOST;
#endif
		hints.ai_family		= AF_UNSPEC;
		hints.ai_socktype	= s->opts.so_type;

		if (0 == getaddrinfo(s->name.host.name, s->name.host.port, &hints, &res0)) {
			assert(res0 != 0);

			for (i = 0, res = res0; res != 0; i++, res = res->ai_next) {
				if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
					goto sysfail;
				else
					ss	= p;

				memcpy(&ss[i], res->ai_addr, res->ai_addrlen);
			}

			if (0 != (so_errno = socket_accept_all(s, i, ss)))
				goto anyfail;

			freeaddrinfo(res0), res0 = 0;
			s->ap->free(s->ap, ss), ss = 0;

			return /* void */;
#else
		if (0 != (ent = gethostbyname(s->name.host.name))) {
			struct sockaddr_in *sin;
			unsigned short port;

			if (!(ss = s->ap->malloc(s->ap, sizeof *ss, 0)))
				goto sysfail;

			sin	= memset(ss, '\0', sizeof *ss);
			sin->sin_family	= AF_INET;
			/* FIXME: Don't use atoi! */
			port		= atoi(s->name.host.port);
			sin->sin_port	= htons(port);

			assert(sizeof sin->sin_addr == ent->h_length);
			(void)memcpy(&sin->sin_addr, ent->h_addr, ent->h_length);

			if (0 != (so_errno = socket_accept_all(s, 1, ss)))
				goto anyfail;

			s->ap->free(s->ap, ss), ss = 0;

			return /* void */;
#endif
		} else {
#ifdef USE_CARES
			if (!(s->lu = lookup_open(0, s->base, s->ap))) {
				/* FIXME: Propogate lookup errors. */
				so_errno	= SOCKET_ESYSTEM;

				goto anyfail;
			}

			a->state	= SOCKET_STATE_LOOKUP;

			lookup_rr(s->lu, s->name.host.name, strlen(s->name.host.name), s->name.host.rtype, s->name.host.flags, &socket_accept_resolved, s, timeout);

			return /* void */;
#else
			so_errno	= SOCKET_ESYSTEM;

			goto anyfail;
#endif /* USE_CARES */
		}
#if !_WIN32
	} else if (s->name.type == &s->name.local) {
		struct sockaddr_storage ss;
		struct sockaddr_un *sun;

		sun		= memset(&ss, 0, sizeof ss);
		sun->sun_family	= AF_UNIX;

		strncpy(sun->sun_path, s->name.local.path, sizeof sun->sun_path);

		if (0 != (so_errno = socket_accept_all(s, 1, &ss)))
			goto anyfail;

		return /* void */;
#endif
	} else if (0 != (so_errno = socket_accept_all(s, 1, &s->name.addr.ss)))
		goto anyfail;

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	sys_errno	= GetLastError();

	socket_cancel(s, 0);

#if USE_IPV6
	if (res0 != 0)
		freeaddrinfo(res0);
#endif

	s->ap->free(s->ap, ss);

	SetLastError(sys_errno);

	cb(s, 0, s->so_errno = so_errno, arg);

	return /* void */;
} /* socket_accept() */


static void socket_connect_throw(struct socket *s, struct socket_connect *c, enum socket_errno so_errno) {
	socket_connect_cb cb	= c->cb.fn;
	void *arg		= c->cb.arg;
	enum bufio_errno bio_errno;
	int sys_errno;

	if (so_errno != 0)
		goto anyfail;

	s->state		= SOCKET_STATE_CONNECTED;
	s->tls.session		= c->tls;
	c->tls			= 0;

	s->peer			= c->peer;
	c->peer.hostname	= 0;

	s->local		= c->local;
	c->local.hostname	= 0;

	s->fd			= c->fd;
	c->fd			= -1;

	LIST_REMOVE(c, le);

	socket_connect_close(s, c, 0);

	if (s->tls.session)
		bufio_socket_switch_tls(&s->iobuf, s->tls.session);

	if (bufio_socket_initialized(&s->iobuf)) {
		bufio_socket_set_fd(&s->iobuf, s->fd);
	} else if (0 == bufio_socket_init(&s->iobuf, s->fd, 0, s->base, s->ap, &bio_errno))
		goto sysfail;

	cb(s, s->so_errno = 0, arg);

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	sys_errno	= GetLastError();

	LIST_REMOVE(c, le);
	
	socket_connect_close(s, c, 0);

	/* Fail anything polling by sliding in an invalid desciptor. */
	if (bufio_socket_initialized(&s->iobuf))
		bufio_socket_set_fd(&s->iobuf, -1);

	SetLastError(sys_errno);

	cb(s, s->so_errno = so_errno, arg);

	return /* void */;
} /* socket_connect_throw() */


static void socket_connect_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
	struct socket *s	= arg;
	struct socket_connect *c;

	assert((c = LIST_FIRST(&s->connect)));

	c->state	= 0;

	/* FIXME: Try to failover to next address if TLS fails? */
	socket_connect_throw(s, c, (tls_errno == 0)? 0 : SOCKET_ENOTLS);

	return /* void */;
} /* socket_connect_catch_tls() */


static void socket_connect_start_tls(struct socket *s, struct socket_connect *c) {
	enum tls_errno tls_errno;

	c->state	= 0;

	if (!s->tls.identity) {
		socket_connect_throw(s, c, 0);

		return /* void */;
	}

	/* FIXME: Bubble up TLS and OpenSSL errors. */
	if (!(c->tls = tls_open(c->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
		socket_connect_throw(s, c, SOCKET_ENOTLS);

		return /* void */;
	}

	c->state	= SOCKET_STATE_START_TLS;

	/* FIXME: Add timeout capability. */
	tls_connect(c->tls, &socket_connect_catch_tls, s, 0);

	return /* void */;
} /* socket_connect_start_tls() */


static void socket_connect_setup(struct socket *, struct socket_connect *);


static void socket_connect_wake(int fd, short event, void *arg) {
	struct socket *s	= arg;
	struct socket_connect *c;
	enum socket_errno so_errno;
	struct timeval now, timeout;
	struct sockaddr *sa;
	socklen_t salen;
	int sd, sys_errno;

	assert((c = LIST_FIRST(&s->connect)));

	c->state	= 0;

	if (event & EV_TIMEOUT)
		goto timeout;

	if (c->ev.pending) {
		int so_errno;
		socklen_t n	= sizeof so_errno;

		c->ev.pending	= 0;	/* Setup as one-shot below. */

		if (0 != getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *)&so_errno, &n))
			goto sysfail;

		if (0 != so_errno) {
			errno	= so_errno;

			goto sysfail;
		}
	} else {
		if (0 != connect(c->fd, SOCKET_SA_P(c->ss.pos), SOCKET_SA_LENOF(c->ss.pos))) {
			switch (GetLastError()) {
#ifdef EINPROGRESS
			case EINPROGRESS:
#endif
#ifdef WSAEINPROGRESS
			case WSAEINPROGRESS:
#endif
#ifdef WSAEWOULDBLOCK
			case WSAEWOULDBLOCK:
#endif
				goto poll;
			default:
				goto sysfail;
			}
		}
	}

	sa	= (struct sockaddr *)&c->peer.ss;
	salen	= sizeof c->peer.ss;

	if (0 != getpeername(c->fd, sa, &salen))
		goto sysfail;

	sa	= (struct sockaddr *)&c->local.ss;
	salen	= sizeof c->local.ss;

	if (0 != getsockname(c->fd, sa, &salen))
		goto sysfail;

	socket_connect_start_tls(s, c);

	return /* void */;
poll:
	if (0 != gettimeofday(&now, 0))
		goto sysfail;

	if (timerisset(&c->expire)) {
		if (timercmp(&now, &c->expire, >=))
			goto timeout;

		timersub(&c->expire, &now, &timeout);
	} else
		timerclear(&timeout);

	event_set(&c->ev.event, c->fd, EV_WRITE, &socket_connect_wake, s);

	if (s->base)
		event_base_set(s->base, &c->ev.event);

	if (0 != event_add(&c->ev.event, timerisset(&timeout)? &timeout : 0))
		goto sysfail;

	c->ev.pending	= 1;

	c->state	= SOCKET_STATE_POLLING;

	return /* void */;
timeout:
	socket_connect_throw(s, c, SOCKET_ETIMEDOUT);

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	if (++c->ss.pos < c->ss.end) {
		if (c->fd != INVALID_SOCKET)
			(void)closesocket(c->fd), c->fd = INVALID_SOCKET;

		socket_connect_setup(s, c);

		return /* void */;
	}

	socket_connect_throw(s, c, so_errno);

	return /* void */;
} /* socket_connect_wake() */


static void socket_connect_setup(struct socket *s, struct socket_connect *c) {
	int sys_errno;

nextsa:
	assert(c->ss.pos < c->ss.end);

	while (c->ss.pos < c->ss.end) {
		if (INVALID_SOCKET != (c->fd = so_open(c->ss.pos, s->opts.so_type)))
			break;
		else if (c->ss.pos >= c->ss.end - 1)
			break;
		else
			c->ss.pos++;
	}

	if (c->fd == INVALID_SOCKET)
		goto sysfail;

	if (s->opts.so_nodelay
	&&  0 != setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&s->opts.so_nodelay, sizeof s->opts.so_nodelay))
		goto sysfail;

	if (0 != so_set_nonblock(c->fd))
		goto sysfail;

	socket_connect_wake(c->fd, EV_WRITE, s);

	return /* void */;
sysfail:
	sys_errno	= GetLastError();

	if (c->fd != INVALID_SOCKET)
		(void)closesocket(c->fd), c->fd = INVALID_SOCKET;

	if (c->ss.pos < c->ss.end - 1)
		goto nextsa;

	SetLastError(sys_errno);

	socket_connect_throw(s, c, SOCKET_ESYSTEM);

	return /* void */;
} /* socket_connect_setup() */


static void socket_connect_resolved(int nans, struct lookup_rr *ans, int nadd, struct lookup_rr *add, enum lookup_errno lu_errno, void *arg) {
	struct socket *s		= arg;
	struct socket_connect *c;
	int sys_errno;
	struct lookup_rr *r;
	struct servent *ent;
	long port;
	char *end;

	assert(c = LIST_FIRST(&s->connect));

	c->state	= 0;

	if (LOOKUP_FAILURE(lu_errno)) {
		goto dnsfail;
	} else if (!(nans > 0)) {
		sys_errno	= EFAULT;

		goto sysfail;
	} else if (!(c->ss.buf = s->ap->malloc(s->ap, nans * sizeof *c->ss.buf, 0)))
		goto sysfail;

	port	= strtol(s->name.host.port, &end, 10);

	if (port < 0 || port > 65535 || *end != '\0') {
		if (!(ent = getservbyname(s->name.host.port, (s->opts.so_type == SOCK_DGRAM)? "udp" : "tcp")))
			goto sysfail;

		port	= ntohs(ent->s_port);
	}

	for (r = ans, c->ss.pos = c->ss.buf; r != 0; r = r->next) {
		switch (r->type) {
		case LOOKUP_IN_A:
			r->rr.ip.sa.sin.sin_port	= htons(port);

			break;
#if USE_IPV6
		case LOOKUP_IN_AAAA:
			r->rr.ip.sa.sin6.sin6_port	= htons(port);

			break;
#endif
		default:
			continue;
		}

		(void)memcpy(c->ss.pos, &r->rr.ip.sa, r->rr.ip.salen);

		c->ss.pos++;
	}

	c->ss.end	= c->ss.pos;
	c->ss.pos	= c->ss.buf;

	if (c->ss.end - c->ss.pos == 0) {
		sys_errno	= EFAULT;

		goto sysfail;
	}

	socket_connect_setup(s, c);

	return /* void */;
sysfail:
	lu_errno	= LOOKUP_ESYSTEM;

	/* FALL THROUGH */
dnsfail:
	switch (lu_errno) {
	case LOOKUP_ESYSTEM:
		socket_connect_throw(s, c, SOCKET_ESYSTEM);

		break;
	case LOOKUP_ETIMEDOUT:
		socket_connect_throw(s, c, SOCKET_ETIMEDOUT);

		break;
	default:
		socket_connect_throw(s, c, SOCKET_ENOTFOUND);

		break;
	}

	return /* void */;
} /* socket_connect_resolved() */


void socket_connect(struct socket *s, socket_connect_cb cb, void *arg, struct timeval *timeout) {
	struct socket_connect *c		= 0;
	struct sockaddr_storage *ss		= 0;
#if USE_IPV6
	struct addrinfo hints, *res, *res0	= 0;
#else
	struct hostent *ent;
#endif
	struct timeval now;
	void *p;
	unsigned i;
	enum socket_errno so_errno;
	int sys_errno;

	assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));

	s->mode	= SOCKET_MODE_CLIENT;

	if (!(c = socket_connect_open(s, &so_errno)))
		goto anyfail;

	LIST_INSERT_HEAD(&s->connect, c, le);

	c->cb.fn	= cb;
	c->cb.arg	= arg;

	if (timeout != 0 && timerisset(timeout)) {
		assert(0 == gettimeofday(&now, 0));

		timeradd(&now, timeout, &c->expire);
	} else
		timerclear(&c->expire);

	if (s->name.type == &s->name.host) {
#if USE_IPV6
		(void)memset(&hints, 0, sizeof hints);

#ifdef USE_CARES
		hints.ai_flags		= AI_NUMERICHOST;
#endif
		hints.ai_family		= AF_UNSPEC;
		hints.ai_socktype	= s->opts.so_type;

		if (0 == getaddrinfo(s->name.host.name, s->name.host.port, &hints, &res0)) {
			assert(res0 != 0);

			for (i = 0, res = res0; res != 0; i++, res = res->ai_next) {
				if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
					goto sysfail;
				else
					ss	= p;

				memcpy(&ss[i], res->ai_addr, res->ai_addrlen);
			}

			c->ss.buf	= ss;
			c->ss.pos	= ss;
			c->ss.end	= ss + i;

			ss		= 0;

			freeaddrinfo(res0), res0 = 0;

			/* FALL THROUGH */
#else
		if (0 != (ent = gethostbyname(s->name.host.name))) {
			struct sockaddr_in *sin;
			unsigned short port;

			if (!(c->ss.buf = s->ap->malloc(s->ap, sizeof *ss, 0)))
				goto sysfail;

			c->ss.pos	= c->ss.buf;
			c->ss.end	= c->ss.pos + 1;
			sin		= memset(c->ss.buf, '\0', sizeof *ss);

			sin->sin_family	= AF_INET;
			/* FIXME: Don't use atoi! */
			port		= atoi(s->name.host.port);
			sin->sin_port	= htons(port);

			assert(sizeof sin->sin_addr == ent->h_length);
			(void)memcpy(&sin->sin_addr, ent->h_addr, ent->h_length);
#endif
		} else {
#ifdef USE_CARES
			if (!(s->lu = lookup_open(0, s->base, s->ap))) {
				/* FIXME: Propogate lookup errors. */
				so_errno	= SOCKET_ESYSTEM;

				goto anyfail;
			}

			c->state	= SOCKET_STATE_LOOKUP;

			lookup_rr(s->lu, s->name.host.name, strlen(s->name.host.name), s->name.host.rtype, s->name.host.flags, &socket_connect_resolved, s, timeout);

			return /* void */;
#else
			so_errno	= SOCKET_ESYSTEM;

			goto anyfail;
#endif /* USE_CARES */
		}
#if !_WIN32
	} else if (s->name.type == &s->name.local) {
		struct sockaddr_un *sun;

		if (0 == (c->ss.buf = s->ap->malloc(s->ap, sizeof *c->ss.buf, 0)))
			goto sysfail;

		c->ss.pos	= c->ss.buf;
		c->ss.end	= c->ss.buf + 1;

		sun		= memset(c->ss.buf, 0, sizeof *c->ss.buf);
		sun->sun_family	= AF_UNIX;

		strncpy(sun->sun_path, s->name.local.path, sizeof sun->sun_path);

		/* FALL THROUGH */
#endif
	} else {
		if (0 == (c->ss.buf = s->ap->malloc(s->ap, sizeof *c->ss.buf, 0)))
			goto sysfail;

		c->ss.pos	= memcpy(c->ss.buf, &s->name.addr.ss, sizeof *c->ss.buf);
		c->ss.end	= c->ss.buf + 1;

		/* FALL THROUGH */
	}

	socket_connect_setup(s, c);

	return /* void */;
sysfail:
	so_errno	= SOCKET_ESYSTEM;

	/* FALL THROUGH */
anyfail:
	sys_errno	= GetLastError();

	socket_cancel(s, 0);

#if USE_IPV6
	if (res0 != 0)
		freeaddrinfo(res0);
#endif

	s->ap->free(s->ap, ss);

	SetLastError(sys_errno);

	cb(s, s->so_errno = so_errno, arg);

	return /* void */;
} /* socket_connect() */


void socket_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
	struct socket *s	= arg;
	void (*cb)(struct socket *, enum socket_errno, void *);

	s->state	= SOCKET_STATE_CONNECTED;

	cb		= s->tls.cb;
	s->tls.cb	= 0;

	arg		= s->tls.arg;
	s->tls.arg	= 0;

	switch (tls_errno) {
	case TLS_ESUCCESS:
		bufio_socket_switch_tls(&s->iobuf, s->tls.session);

		cb(s, s->so_errno = SOCKET_ESUCCESS, arg);

		break;
	case TLS_ETIMEDOUT:
		cb(s, s->so_errno = SOCKET_ETIMEDOUT, arg);

		break;
	default:
		cb(s, s->so_errno = SOCKET_ENOTLS, arg);

		break;
	}

	return /* void */;
} /* socket_catch_tls() */


void socket_start_tls(struct socket *s, struct tls_identity *tlsid, void (*cb)(struct socket *, enum socket_errno, void *), void *arg, struct timeval *timeout) {
	enum tls_errno tls_errno;

	assert(!s->tls.session);

	s->tls.identity	= tlsid;

	if (!(s->tls.session = tls_open(s->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
		cb(s, s->so_errno = SOCKET_ENOTLS, arg);

		return /* void */;
	}

	s->state	= SOCKET_STATE_START_TLS;

	s->tls.cb	= cb;
	s->tls.arg	= arg;

	if (s->mode == SOCKET_MODE_SERVER)
		tls_accept(s->tls.session, &socket_catch_tls, s, timeout);
	else
		tls_connect(s->tls.session, &socket_catch_tls, s, timeout);

	return /* void */;
} /* socket_start_tls() */


struct socket_name *socket_name_init_addr(struct socket_name *name, const struct sockaddr *sa, socklen_t salen) {
	name->type	= &name->addr;

	(void)memcpy(&name->addr.sa, sa, salen);

	return name;
} /* socket_name_init_addr() */


struct socket_name *socket_name_init_host(struct socket_name *name, const char *host, const char *port, int rtype, int flags) {
	name->type	= &name->host;

	(void)snprintf(name->host.name, sizeof name->host.name, "%s", host);
	(void)snprintf(name->host.port, sizeof name->host.port, "%s", port);

	name->host.rtype	= rtype;
	name->host.flags	= flags;

	return name;
} /* socket_name_init_host() */


#if !_WIN32
struct socket_name *socket_name_init_unix(struct socket_name *name, const char *path) {
	name->type	= &name->local;

	(void)snprintf(name->local.path, sizeof name->local.path, "%s", path);

	return name;
} /* socket_name_init_unix() */
#endif


enum socket_errno socket_errno(struct socket *s) {
	return s->so_errno;
} /* socket_errno() */


const char *socket_strerror(enum socket_errno so_errno) {
	return socket_errlist[so_errno];
} /* socket_strerror() */


const char *socket_errstring(struct socket *s) {
	return socket_errlist[s->so_errno];
} /* socket_errstring() */


void socket_getoption_so_type(struct socket *s, int *t) {
	*t	= s->opts.so_type;

	return /* void */;
} /* socket_getoption_so_type() */


void socket_getoption_so_nonblock(struct socket *s, int *b) {
	*b	= s->opts.so_nonblock;

	return /* void */;
} /* socket_getoption_so_nonblock() */


void socket_getoption_so_mode(struct socket *s, enum socket_mode *m) {
	*m	= s->mode;

	return /* void */;
} /* socket_getoption_so_mode() */



syntax highlighted by Code2HTML, v. 0.9.1