Commit Diff


commit - /dev/null
commit + ce062d502fe54f3757676855142be3d2eca13ed0
blob - /dev/null
blob + 864603f9f811412dcb93f4dce43c90cb99532c73 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,43 @@
+#	$OpenBSD: Makefile,v 1.33 2018/02/08 05:56:49 jsing Exp $
+
+CFLAGS+=	-Wall -I${.CURDIR}
+CFLAGS+=	-Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=	-Wmissing-declarations
+CFLAGS+=	-Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+=	-Wsign-compare
+
+CLEANFILES= ${VERSION_SCRIPT}
+
+WARNINGS= Yes
+
+LIB=	opensmtpd
+
+#DPADD= ${LIBEVENT}
+
+#LDADD+= -levent
+
+
+VERSION_SCRIPT=	Symbols.map
+SYMBOL_LIST=	${.CURDIR}/Symbols.list
+
+HDRS=	opensmtpd.h
+
+SRCS=	opensmtpd.c \
+	iobuf.c \
+	ioev.c
+
+includes:
+	@cd ${.CURDIR}; for i in $(HDRS); do \
+	    j="cmp -s $$i ${DESTDIR}/usr/include/$$i || \
+	    ${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} -m 444 $$i\
+		${DESTDIR}/usr/include/"; \
+	    echo $$j; \
+	    eval "$$j"; \
+	done;
+
+${VERSION_SCRIPT}: ${SYMBOL_LIST}
+	{ printf '{\n\tglobal:\n'; \
+	  sed '/^[._a-zA-Z]/s/$$/;/; s/^/		/' ${SYMBOL_LIST}; \
+	  printf '\n\tlocal:\n\t\t*;\n};\n'; } >$@.tmp && mv $@.tmp $@
+
+.include <bsd.lib.mk>
blob - /dev/null
blob + 1da9ce37515d8e8047a437bfda5a062f4127a35a (mode 644)
--- /dev/null
+++ Symbols.list
@@ -0,0 +1,36 @@
+osmtpd_register_filter_connect
+osmtpd_register_filter_helo
+osmtpd_register_filter_ehlo
+osmtpd_register_filter_starttls
+osmtpd_register_filter_auth
+osmtpd_register_filter_mailfrom
+osmtpd_register_filter_rcptto
+osmtpd_register_filter_data
+osmtpd_register_filter_dataline
+osmtpd_register_filter_rset
+osmtpd_register_filter_quit
+osmtpd_register_filter_noop
+osmtpd_register_filter_help
+osmtpd_register_filter_wiz
+osmtpd_register_filter_commit
+osmtpd_register_report_connect
+osmtpd_register_report_disconnect
+osmtpd_register_report_identify
+osmtpd_register_report_tls
+osmtpd_register_report_begin
+osmtpd_register_report_mail
+osmtpd_register_report_rcpt
+osmtpd_register_report_envelope
+osmtpd_register_report_data
+osmtpd_register_report_commit
+osmtpd_register_report_rollback
+osmtpd_register_report_client
+osmtpd_register_report_server
+osmtpd_register_report_response
+osmtpd_register_report_timeout
+osmtpd_filter_proceed
+osmtpd_filter_reject
+osmtpd_filter_disconnect
+osmtpd_filter_dataline
+osmtpd_localdata
+osmtpd_run
blob - /dev/null
blob + 38aa0b6138838f56fe1a277c6321c0bdff6e0ea4 (mode 644)
--- /dev/null
+++ iobuf.c
@@ -0,0 +1,466 @@
+/*	$OpenBSD: iobuf.c,v 1.11 2019/06/12 17:42:53 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef IO_TLS
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#endif
+
+#include "iobuf.h"
+
+#define IOBUF_MAX	65536
+#define IOBUFQ_MIN	4096
+
+struct ioqbuf	*ioqbuf_alloc(struct iobuf *, size_t);
+void		 iobuf_drain(struct iobuf *, size_t);
+
+int
+iobuf_init(struct iobuf *io, size_t size, size_t max)
+{
+	memset(io, 0, sizeof *io);
+
+	if (max == 0)
+		max = IOBUF_MAX;
+
+	if (size == 0)
+		size = max;
+
+	if (size > max)
+		return (-1);
+
+	if ((io->buf = calloc(size, 1)) == NULL)
+		return (-1);
+
+	io->size = size;
+	io->max = max;
+
+	return (0);
+}
+
+void
+iobuf_clear(struct iobuf *io)
+{
+	struct ioqbuf	*q;
+
+	free(io->buf);
+
+	while ((q = io->outq)) {
+		io->outq = q->next;
+		free(q);
+	}
+
+	memset(io, 0, sizeof (*io));
+}
+
+void
+iobuf_drain(struct iobuf *io, size_t n)
+{
+	struct	ioqbuf	*q;
+	size_t		 left = n;
+
+	while ((q = io->outq) && left) {
+		if ((q->wpos - q->rpos) > left) {
+			q->rpos += left;
+			left = 0;
+		} else {
+			left -= q->wpos - q->rpos;
+			io->outq = q->next;
+			free(q);
+		}
+	}
+
+	io->queued -= (n - left);
+	if (io->outq == NULL)
+		io->outqlast = NULL;
+}
+
+int
+iobuf_extend(struct iobuf *io, size_t n)
+{
+	char	*t;
+
+	if (n > io->max)
+		return (-1);
+
+	if (io->max - io->size < n)
+		return (-1);
+
+	t = recallocarray(io->buf, io->size, io->size + n, 1);
+	if (t == NULL)
+		return (-1);
+
+	io->size += n;
+	io->buf = t;
+
+	return (0);
+}
+
+size_t
+iobuf_left(struct iobuf *io)
+{
+	return io->size - io->wpos;
+}
+
+size_t
+iobuf_space(struct iobuf *io)
+{
+	return io->size - (io->wpos - io->rpos);
+}
+
+size_t
+iobuf_len(struct iobuf *io)
+{
+	return io->wpos - io->rpos;
+}
+
+char *
+iobuf_data(struct iobuf *io)
+{
+	return io->buf + io->rpos;
+}
+
+void
+iobuf_drop(struct iobuf *io, size_t n)
+{
+	if (n >= iobuf_len(io)) {
+		io->rpos = io->wpos = 0;
+		return;
+	}
+
+	io->rpos += n;
+}
+
+char *
+iobuf_getline(struct iobuf *iobuf, size_t *rlen)
+{
+	char	*buf;
+	size_t	 len, i;
+
+	buf = iobuf_data(iobuf);
+	len = iobuf_len(iobuf);
+
+	for (i = 0; i + 1 <= len; i++)
+		if (buf[i] == '\n') {
+			/* Note: the returned address points into the iobuf
+			 * buffer.  We NUL-end it for convenience, and discard
+			 * the data from the iobuf, so that the caller doesn't
+			 * have to do it.  The data remains "valid" as long
+			 * as the iobuf does not overwrite it, that is until
+			 * the next call to iobuf_normalize() or iobuf_extend().
+			 */
+			iobuf_drop(iobuf, i + 1);
+			len = (i && buf[i - 1] == '\r') ? i - 1 : i;
+			buf[len] = '\0';
+			if (rlen)
+				*rlen = len;
+			return (buf);
+		}
+
+	return (NULL);
+}
+
+void
+iobuf_normalize(struct iobuf *io)
+{
+	if (io->rpos == 0)
+		return;
+
+	if (io->rpos == io->wpos) {
+		io->rpos = io->wpos = 0;
+		return;
+	}
+
+	memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos);
+	io->wpos -= io->rpos;
+	io->rpos = 0;
+}
+
+ssize_t
+iobuf_read(struct iobuf *io, int fd)
+{
+	ssize_t	n;
+
+	n = read(fd, io->buf + io->wpos, iobuf_left(io));
+	if (n == -1) {
+		/* XXX is this really what we want? */
+		if (errno == EAGAIN || errno == EINTR)
+			return (IOBUF_WANT_READ);
+		return (IOBUF_ERROR);
+	}
+	if (n == 0)
+		return (IOBUF_CLOSED);
+
+	io->wpos += n;
+
+	return (n);
+}
+
+struct ioqbuf *
+ioqbuf_alloc(struct iobuf *io, size_t len)
+{
+	struct ioqbuf   *q;
+
+	if (len < IOBUFQ_MIN)
+		len = IOBUFQ_MIN;
+
+	if ((q = malloc(sizeof(*q) + len)) == NULL)
+		return (NULL);
+
+	q->rpos = 0;
+	q->wpos = 0;
+	q->size = len;
+	q->next = NULL;
+	q->buf = (char *)(q) + sizeof(*q);
+
+	if (io->outqlast == NULL)
+		io->outq = q;
+	else
+		io->outqlast->next = q;
+	io->outqlast = q;
+
+	return (q);
+}
+
+size_t
+iobuf_queued(struct iobuf *io)
+{
+	return io->queued;
+}
+
+void *
+iobuf_reserve(struct iobuf *io, size_t len)
+{
+	struct ioqbuf	*q;
+	void		*r;
+
+	if (len == 0)
+		return (NULL);
+
+	if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) {
+		if ((q = ioqbuf_alloc(io, len)) == NULL)
+			return (NULL);
+	}
+
+	r = q->buf + q->wpos;
+	q->wpos += len;
+	io->queued += len;
+
+	return (r);
+}
+
+int
+iobuf_queue(struct iobuf *io, const void *data, size_t len)
+{
+	void	*buf;
+
+	if (len == 0)
+		return (0);
+
+	if ((buf = iobuf_reserve(io, len)) == NULL)
+		return (-1);
+
+	memmove(buf, data, len);
+
+	return (len);
+}
+
+int
+iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt)
+{
+	int	 i;
+	size_t	 len = 0;
+	char	*buf;
+
+	for (i = 0; i < iovcnt; i++)
+		len += iov[i].iov_len;
+
+	if ((buf = iobuf_reserve(io, len)) == NULL)
+		return (-1);
+
+	for (i = 0; i < iovcnt; i++) {
+		if (iov[i].iov_len == 0)
+			continue;
+		memmove(buf, iov[i].iov_base, iov[i].iov_len);
+		buf += iov[i].iov_len;
+	}
+
+	return (0);
+
+}
+
+int
+iobuf_fqueue(struct iobuf *io, const char *fmt, ...)
+{
+	va_list	ap;
+	int	len;
+
+	va_start(ap, fmt);
+	len = iobuf_vfqueue(io, fmt, ap);
+	va_end(ap);
+
+	return (len);
+}
+
+int
+iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap)
+{
+	char	*buf;
+	int	 len;
+
+	len = vasprintf(&buf, fmt, ap);
+
+	if (len == -1)
+		return (-1);
+
+	len = iobuf_queue(io, buf, len);
+	free(buf);
+
+	return (len);
+}
+
+ssize_t
+iobuf_write(struct iobuf *io, int fd)
+{
+	struct iovec	 iov[IOV_MAX];
+	struct ioqbuf	*q;
+	int		 i;
+	ssize_t		 n;
+
+	i = 0;
+	for (q = io->outq; q ; q = q->next) {
+		if (i >= IOV_MAX)
+			break;
+		iov[i].iov_base = q->buf + q->rpos;
+		iov[i].iov_len = q->wpos - q->rpos;
+		i++;
+	}
+
+	n = writev(fd, iov, i);
+	if (n == -1) {
+		if (errno == EAGAIN || errno == EINTR)
+			return (IOBUF_WANT_WRITE);
+		if (errno == EPIPE)
+			return (IOBUF_CLOSED);
+		return (IOBUF_ERROR);
+	}
+
+	iobuf_drain(io, n);
+
+	return (n);
+}
+
+int
+iobuf_flush(struct iobuf *io, int fd)
+{
+	ssize_t	s;
+
+	while (io->queued)
+		if ((s = iobuf_write(io, fd)) < 0)
+			return (s);
+
+	return (0);
+}
+
+#ifdef IO_TLS
+
+int
+iobuf_flush_tls(struct iobuf *io, void *tls)
+{
+	ssize_t	s;
+
+	while (io->queued)
+		if ((s = iobuf_write_tls(io, tls)) < 0)
+			return (s);
+
+	return (0);
+}
+
+ssize_t
+iobuf_write_tls(struct iobuf *io, void *tls)
+{
+	struct ioqbuf	*q;
+	int		 r;
+	ssize_t		 n;
+
+	q = io->outq;
+	n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos);
+	if (n <= 0) {
+		switch ((r = SSL_get_error(tls, n))) {
+		case SSL_ERROR_WANT_READ:
+			return (IOBUF_WANT_READ);
+		case SSL_ERROR_WANT_WRITE:
+			return (IOBUF_WANT_WRITE);
+		case SSL_ERROR_ZERO_RETURN: /* connection closed */
+			return (IOBUF_CLOSED);
+		case SSL_ERROR_SYSCALL:
+			if (ERR_peek_last_error())
+				return (IOBUF_TLSERROR);
+			if (r == 0)
+				errno = EPIPE;
+			return (IOBUF_ERROR);
+		default:
+			return (IOBUF_TLSERROR);
+		}
+	}
+	iobuf_drain(io, n);
+
+	return (n);
+}
+
+ssize_t
+iobuf_read_tls(struct iobuf *io, void *tls)
+{
+	ssize_t	n;
+	int	r;
+
+	n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io));
+	if (n < 0) {
+		switch ((r = SSL_get_error(tls, n))) {
+		case SSL_ERROR_WANT_READ:
+			return (IOBUF_WANT_READ);
+		case SSL_ERROR_WANT_WRITE:
+			return (IOBUF_WANT_WRITE);
+		case SSL_ERROR_SYSCALL:
+			if (ERR_peek_last_error())
+				return (IOBUF_TLSERROR);
+			if (r == 0)
+				errno = EPIPE;
+			return (IOBUF_ERROR);
+		default:
+			return (IOBUF_TLSERROR);
+		}
+	} else if (n == 0)
+		return (IOBUF_CLOSED);
+
+	io->wpos += n;
+
+	return (n);
+}
+
+#endif /* IO_TLS */
blob - /dev/null
blob + c454d0a14077f5b78aa1e60b285b112a0257d9bb (mode 644)
--- /dev/null
+++ iobuf.h
@@ -0,0 +1,67 @@
+/*	$OpenBSD: iobuf.h,v 1.5 2019/06/12 17:42:53 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct ioqbuf {
+	struct ioqbuf	*next;
+	char		*buf;
+	size_t		 size;
+	size_t		 wpos;
+	size_t		 rpos;
+};
+
+struct iobuf {
+	char		*buf;
+	size_t		 max;
+	size_t		 size;
+	size_t		 wpos;
+	size_t		 rpos;
+
+	size_t		 queued;
+	struct ioqbuf	*outq;
+	struct ioqbuf	*outqlast;
+};
+
+#define IOBUF_WANT_READ		-1
+#define IOBUF_WANT_WRITE	-2
+#define IOBUF_CLOSED		-3
+#define IOBUF_ERROR		-4
+#define IOBUF_TLSERROR		-5
+
+int	iobuf_init(struct iobuf *, size_t, size_t);
+void	iobuf_clear(struct iobuf *);
+
+int	iobuf_extend(struct iobuf *, size_t);
+void	iobuf_normalize(struct iobuf *);
+void	iobuf_drop(struct iobuf *, size_t);
+size_t	iobuf_space(struct iobuf *);
+size_t	iobuf_len(struct iobuf *);
+size_t	iobuf_left(struct iobuf *);
+char   *iobuf_data(struct iobuf *);
+char   *iobuf_getline(struct iobuf *, size_t *);
+ssize_t	iobuf_read(struct iobuf *, int);
+ssize_t	iobuf_read_tls(struct iobuf *, void *);
+
+size_t  iobuf_queued(struct iobuf *);
+void*   iobuf_reserve(struct iobuf *, size_t);
+int	iobuf_queue(struct iobuf *, const void*, size_t);
+int	iobuf_queuev(struct iobuf *, const struct iovec *, int);
+int	iobuf_fqueue(struct iobuf *, const char *, ...);
+int	iobuf_vfqueue(struct iobuf *, const char *, va_list);
+int	iobuf_flush(struct iobuf *, int);
+int	iobuf_flush_tls(struct iobuf *, void *);
+ssize_t	iobuf_write(struct iobuf *, int);
+ssize_t	iobuf_write_tls(struct iobuf *, void *);
blob - /dev/null
blob + d36210fd7dd61cb8b90377276b985ae62690f370 (mode 644)
--- /dev/null
+++ ioev.c
@@ -0,0 +1,1062 @@
+/*	$OpenBSD: ioev.c,v 1.42 2019/06/12 17:42:53 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "ioev.h"
+#include "iobuf.h"
+
+#ifdef IO_TLS
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#endif
+
+enum {
+	IO_STATE_NONE,
+	IO_STATE_CONNECT,
+	IO_STATE_CONNECT_TLS,
+	IO_STATE_ACCEPT_TLS,
+	IO_STATE_UP,
+
+	IO_STATE_MAX,
+};
+
+#define IO_PAUSE_IN 		IO_IN
+#define IO_PAUSE_OUT		IO_OUT
+#define IO_READ			0x04
+#define IO_WRITE		0x08
+#define IO_RW			(IO_READ | IO_WRITE)
+#define IO_RESET		0x10  /* internal */
+#define IO_HELD			0x20  /* internal */
+
+struct io {
+	int		 sock;
+	void		*arg;
+	void		(*cb)(struct io*, int, void *);
+	struct iobuf	 iobuf;
+	size_t		 lowat;
+	int		 timeout;
+	int		 flags;
+	int		 state;
+	struct event	 ev;
+	void		*tls;
+	const char	*error; /* only valid immediately on callback */
+};
+
+const char* io_strflags(int);
+const char* io_evstr(short);
+
+void	_io_init(void);
+void	io_hold(struct io *);
+void	io_release(struct io *);
+void	io_callback(struct io*, int);
+void	io_dispatch(int, short, void *);
+void	io_dispatch_connect(int, short, void *);
+size_t	io_pending(struct io *);
+size_t	io_queued(struct io*);
+void	io_reset(struct io *, short, void (*)(int, short, void*));
+void	io_frame_enter(const char *, struct io *, int);
+void	io_frame_leave(struct io *);
+
+#ifdef IO_TLS
+void	ssl_error(const char *); /* XXX external */
+
+static const char* io_tls_error(void);
+void	io_dispatch_accept_tls(int, short, void *);
+void	io_dispatch_connect_tls(int, short, void *);
+void	io_dispatch_read_tls(int, short, void *);
+void	io_dispatch_write_tls(int, short, void *);
+void	io_reload_tls(struct io *io);
+#endif
+
+static struct io	*current = NULL;
+static uint64_t		 frame = 0;
+static int		_io_debug = 0;
+
+#define io_debug(args...) do { if (_io_debug) printf(args); } while(0)
+
+
+const char*
+io_strio(struct io *io)
+{
+	static char	buf[128];
+	char		ssl[128];
+
+	ssl[0] = '\0';
+#ifdef IO_TLS
+	if (io->tls) {
+		(void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d",
+		    SSL_get_version(io->tls),
+		    SSL_get_cipher_name(io->tls),
+		    SSL_get_cipher_bits(io->tls, NULL));
+	}
+#endif
+
+	(void)snprintf(buf, sizeof buf,
+	    "<io:%p fd=%d to=%d fl=%s%s ib=%zu ob=%zu>",
+	    io, io->sock, io->timeout, io_strflags(io->flags), ssl,
+	    io_pending(io), io_queued(io));
+
+	return (buf);
+}
+
+#define CASE(x) case x : return #x
+
+const char*
+io_strevent(int evt)
+{
+	static char buf[32];
+
+	switch (evt) {
+	CASE(IO_CONNECTED);
+	CASE(IO_TLSREADY);
+	CASE(IO_DATAIN);
+	CASE(IO_LOWAT);
+	CASE(IO_DISCONNECTED);
+	CASE(IO_TIMEOUT);
+	CASE(IO_ERROR);
+	default:
+		(void)snprintf(buf, sizeof(buf), "IO_? %d", evt);
+		return buf;
+	}
+}
+
+void
+io_set_nonblocking(int fd)
+{
+	int	flags;
+
+	if ((flags = fcntl(fd, F_GETFL)) == -1)
+		err(1, "io_set_blocking:fcntl(F_GETFL)");
+
+	flags |= O_NONBLOCK;
+
+	if (fcntl(fd, F_SETFL, flags) == -1)
+		err(1, "io_set_blocking:fcntl(F_SETFL)");
+}
+
+void
+io_set_nolinger(int fd)
+{
+	struct linger    l;
+
+	memset(&l, 0, sizeof(l));
+	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1)
+		err(1, "io_set_linger:setsockopt()");
+}
+
+/*
+ * Event framing must not rely on an io pointer to refer to the "same" io
+ * throughout the frame, because this is not always the case:
+ *
+ * 1) enter(addr0) -> free(addr0) -> leave(addr0) = SEGV
+ * 2) enter(addr0) -> free(addr0) -> malloc == addr0 -> leave(addr0) = BAD!
+ *
+ * In both case, the problem is that the io is freed in the callback, so
+ * the pointer becomes invalid. If that happens, the user is required to
+ * call io_clear, so we can adapt the frame state there.
+ */
+void
+io_frame_enter(const char *where, struct io *io, int ev)
+{
+	io_debug("\n=== %" PRIu64 " ===\n"
+	    "io_frame_enter(%s, %s, %s)\n",
+	    frame, where, io_evstr(ev), io_strio(io));
+
+	if (current)
+		errx(1, "io_frame_enter: interleaved frames");
+
+	current = io;
+
+	io_hold(io);
+}
+
+void
+io_frame_leave(struct io *io)
+{
+	io_debug("io_frame_leave(%" PRIu64 ")\n", frame);
+
+	if (current && current != io)
+		errx(1, "io_frame_leave: io mismatch");
+
+	/* io has been cleared */
+	if (current == NULL)
+		goto done;
+
+	/* TODO: There is a possible optimization there:
+	 * In a typical half-duplex request/response scenario,
+	 * the io is waiting to read a request, and when done, it queues
+	 * the response in the output buffer and goes to write mode.
+	 * There, the write event is set and will be triggered in the next
+	 * event frame.  In most case, the write call could be done
+	 * immediately as part of the last read frame, thus avoiding to go
+	 * through the event loop machinery. So, as an optimisation, we
+	 * could detect that case here and force an event dispatching.
+	 */
+
+	/* Reload the io if it has not been reset already. */
+	io_release(io);
+	current = NULL;
+    done:
+	io_debug("=== /%" PRIu64 "\n", frame);
+
+	frame += 1;
+}
+
+void
+_io_init()
+{
+	static int init = 0;
+
+	if (init)
+		return;
+
+	init = 1;
+	_io_debug = getenv("IO_DEBUG") != NULL;
+}
+
+struct io *
+io_new(void)
+{
+	struct io *io;
+
+	_io_init();
+
+	if ((io = calloc(1, sizeof(*io))) == NULL)
+		return NULL;
+
+	io->sock = -1;
+	io->timeout = -1;
+
+	if (iobuf_init(&io->iobuf, 0, 0) == -1) {
+		free(io);
+		return NULL;
+	}
+
+	return io;
+}
+
+void
+io_free(struct io *io)
+{
+	io_debug("io_clear(%p)\n", io);
+
+	/* the current io is virtually dead */
+	if (io == current)
+		current = NULL;
+
+#ifdef IO_TLS
+	SSL_free(io->tls);
+	io->tls = NULL;
+#endif
+
+	if (event_initialized(&io->ev))
+		event_del(&io->ev);
+	if (io->sock != -1) {
+		close(io->sock);
+		io->sock = -1;
+	}
+
+	iobuf_clear(&io->iobuf);
+	free(io);
+}
+
+void
+io_hold(struct io *io)
+{
+	io_debug("io_enter(%p)\n", io);
+
+	if (io->flags & IO_HELD)
+		errx(1, "io_hold: io is already held");
+
+	io->flags &= ~IO_RESET;
+	io->flags |= IO_HELD;
+}
+
+void
+io_release(struct io *io)
+{
+	if (!(io->flags & IO_HELD))
+		errx(1, "io_release: io is not held");
+
+	io->flags &= ~IO_HELD;
+	if (!(io->flags & IO_RESET))
+		io_reload(io);
+}
+
+void
+io_set_fd(struct io *io, int fd)
+{
+	io->sock = fd;
+	if (fd != -1)
+		io_reload(io);
+}
+
+void
+io_set_callback(struct io *io, void(*cb)(struct io *, int, void *), void *arg)
+{
+	io->cb = cb;
+	io->arg = arg;
+}
+
+void
+io_set_timeout(struct io *io, int msec)
+{
+	io_debug("io_set_timeout(%p, %d)\n", io, msec);
+
+	io->timeout = msec;
+}
+
+void
+io_set_lowat(struct io *io, size_t lowat)
+{
+	io_debug("io_set_lowat(%p, %zu)\n", io, lowat);
+
+	io->lowat = lowat;
+}
+
+void
+io_pause(struct io *io, int dir)
+{
+	io_debug("io_pause(%p, %x)\n", io, dir);
+
+	io->flags |= dir & (IO_PAUSE_IN | IO_PAUSE_OUT);
+	io_reload(io);
+}
+
+void
+io_resume(struct io *io, int dir)
+{
+	io_debug("io_resume(%p, %x)\n", io, dir);
+
+	io->flags &= ~(dir & (IO_PAUSE_IN | IO_PAUSE_OUT));
+	io_reload(io);
+}
+
+void
+io_set_read(struct io *io)
+{
+	int	mode;
+
+	io_debug("io_set_read(%p)\n", io);
+
+	mode = io->flags & IO_RW;
+	if (!(mode == 0 || mode == IO_WRITE))
+		errx(1, "io_set_read(): full-duplex or reading");
+
+	io->flags &= ~IO_RW;
+	io->flags |= IO_READ;
+	io_reload(io);
+}
+
+void
+io_set_write(struct io *io)
+{
+	int	mode;
+
+	io_debug("io_set_write(%p)\n", io);
+
+	mode = io->flags & IO_RW;
+	if (!(mode == 0 || mode == IO_READ))
+		errx(1, "io_set_write(): full-duplex or writing");
+
+	io->flags &= ~IO_RW;
+	io->flags |= IO_WRITE;
+	io_reload(io);
+}
+
+const char *
+io_error(struct io *io)
+{
+	return io->error;
+}
+
+void *
+io_tls(struct io *io)
+{
+	return io->tls;
+}
+
+int
+io_fileno(struct io *io)
+{
+	return io->sock;
+}
+
+int
+io_paused(struct io *io, int what)
+{
+	return (io->flags & (IO_PAUSE_IN | IO_PAUSE_OUT)) == what;
+}
+
+/*
+ * Buffered output functions
+ */
+
+int
+io_write(struct io *io, const void *buf, size_t len)
+{
+	int r;
+
+	r = iobuf_queue(&io->iobuf, buf, len);
+
+	io_reload(io);
+
+	return r;
+}
+
+int
+io_writev(struct io *io, const struct iovec *iov, int iovcount)
+{
+	int r;
+
+	r = iobuf_queuev(&io->iobuf, iov, iovcount);
+
+	io_reload(io);
+
+	return r;
+}
+
+int
+io_print(struct io *io, const char *s)
+{
+	return io_write(io, s, strlen(s));
+}
+
+int
+io_printf(struct io *io, const char *fmt, ...)
+{
+	va_list ap;
+	int r;
+
+	va_start(ap, fmt);
+	r = io_vprintf(io, fmt, ap);
+	va_end(ap);
+
+	return r;
+}
+
+int
+io_vprintf(struct io *io, const char *fmt, va_list ap)
+{
+
+	char *buf;
+	int len;
+
+	len = vasprintf(&buf, fmt, ap);
+	if (len == -1)
+		return -1;
+	len = io_write(io, buf, len);
+	free(buf);
+
+	return len;
+}
+
+size_t
+io_queued(struct io *io)
+{
+	return iobuf_queued(&io->iobuf);
+}
+
+/*
+ * Buffered input functions
+ */
+
+void *
+io_data(struct io *io)
+{
+	return iobuf_data(&io->iobuf);
+}
+
+size_t
+io_datalen(struct io *io)
+{
+	return iobuf_len(&io->iobuf);
+}
+
+char *
+io_getline(struct io *io, size_t *sz)
+{
+	return iobuf_getline(&io->iobuf, sz);
+}
+
+void
+io_drop(struct io *io, size_t sz)
+{
+	return iobuf_drop(&io->iobuf, sz);
+}
+
+
+#define IO_READING(io) (((io)->flags & IO_RW) != IO_WRITE)
+#define IO_WRITING(io) (((io)->flags & IO_RW) != IO_READ)
+
+/*
+ * Setup the necessary events as required by the current io state,
+ * honouring duplex mode and i/o pauses.
+ */
+void
+io_reload(struct io *io)
+{
+	short	events;
+
+	/* io will be reloaded at release time */
+	if (io->flags & IO_HELD)
+		return;
+
+	iobuf_normalize(&io->iobuf);
+
+#ifdef IO_TLS
+	if (io->tls) {
+		io_reload_tls(io);
+		return;
+	}
+#endif
+
+	io_debug("io_reload(%p)\n", io);
+
+	events = 0;
+	if (IO_READING(io) && !(io->flags & IO_PAUSE_IN))
+		events = EV_READ;
+	if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io))
+		events |= EV_WRITE;
+
+	io_reset(io, events, io_dispatch);
+}
+
+/* Set the requested event. */
+void
+io_reset(struct io *io, short events, void (*dispatch)(int, short, void*))
+{
+	struct timeval	tv, *ptv;
+
+	io_debug("io_reset(%p, %s, %p) -> %s\n",
+	    io, io_evstr(events), dispatch, io_strio(io));
+
+	/*
+	 * Indicate that the event has already been reset so that reload
+	 * is not called on frame_leave.
+	 */
+	io->flags |= IO_RESET;
+
+	if (event_initialized(&io->ev))
+		event_del(&io->ev);
+
+	/*
+	 * The io is paused by the user, so we don't want the timeout to be
+	 * effective.
+	 */
+	if (events == 0)
+		return;
+
+	event_set(&io->ev, io->sock, events, dispatch, io);
+	if (io->timeout >= 0) {
+		tv.tv_sec = io->timeout / 1000;
+		tv.tv_usec = (io->timeout % 1000) * 1000;
+		ptv = &tv;
+	} else
+		ptv = NULL;
+
+	event_add(&io->ev, ptv);
+}
+
+size_t
+io_pending(struct io *io)
+{
+	return iobuf_len(&io->iobuf);
+}
+
+const char*
+io_strflags(int flags)
+{
+	static char	buf[64];
+
+	buf[0] = '\0';
+
+	switch (flags & IO_RW) {
+	case 0:
+		(void)strlcat(buf, "rw", sizeof buf);
+		break;
+	case IO_READ:
+		(void)strlcat(buf, "R", sizeof buf);
+		break;
+	case IO_WRITE:
+		(void)strlcat(buf, "W", sizeof buf);
+		break;
+	case IO_RW:
+		(void)strlcat(buf, "RW", sizeof buf);
+		break;
+	}
+
+	if (flags & IO_PAUSE_IN)
+		(void)strlcat(buf, ",F_PI", sizeof buf);
+	if (flags & IO_PAUSE_OUT)
+		(void)strlcat(buf, ",F_PO", sizeof buf);
+
+	return buf;
+}
+
+const char*
+io_evstr(short ev)
+{
+	static char	buf[64];
+	char		buf2[16];
+	int		n;
+
+	n = 0;
+	buf[0] = '\0';
+
+	if (ev == 0) {
+		(void)strlcat(buf, "<NONE>", sizeof(buf));
+		return buf;
+	}
+
+	if (ev & EV_TIMEOUT) {
+		(void)strlcat(buf, "EV_TIMEOUT", sizeof(buf));
+		ev &= ~EV_TIMEOUT;
+		n++;
+	}
+
+	if (ev & EV_READ) {
+		if (n)
+			(void)strlcat(buf, "|", sizeof(buf));
+		(void)strlcat(buf, "EV_READ", sizeof(buf));
+		ev &= ~EV_READ;
+		n++;
+	}
+
+	if (ev & EV_WRITE) {
+		if (n)
+			(void)strlcat(buf, "|", sizeof(buf));
+		(void)strlcat(buf, "EV_WRITE", sizeof(buf));
+		ev &= ~EV_WRITE;
+		n++;
+	}
+
+	if (ev & EV_SIGNAL) {
+		if (n)
+			(void)strlcat(buf, "|", sizeof(buf));
+		(void)strlcat(buf, "EV_SIGNAL", sizeof(buf));
+		ev &= ~EV_SIGNAL;
+		n++;
+	}
+
+	if (ev) {
+		if (n)
+			(void)strlcat(buf, "|", sizeof(buf));
+		(void)strlcat(buf, "EV_?=0x", sizeof(buf));
+		(void)snprintf(buf2, sizeof(buf2), "%hx", ev);
+		(void)strlcat(buf, buf2, sizeof(buf));
+	}
+
+	return buf;
+}
+
+void
+io_dispatch(int fd, short ev, void *humppa)
+{
+	struct io	*io = humppa;
+	size_t		 w;
+	ssize_t		 n;
+	int		 saved_errno;
+
+	io_frame_enter("io_dispatch", io, ev);
+
+	if (ev == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	if (ev & EV_WRITE && (w = io_queued(io))) {
+		if ((n = iobuf_write(&io->iobuf, io->sock)) < 0) {
+			if (n == IOBUF_WANT_WRITE) /* kqueue bug? */
+				goto read;
+			if (n == IOBUF_CLOSED)
+				io_callback(io, IO_DISCONNECTED);
+			else {
+				saved_errno = errno;
+				io->error = strerror(errno);
+				errno = saved_errno;
+				io_callback(io, IO_ERROR);
+			}
+			goto leave;
+		}
+		if (w > io->lowat && w - n <= io->lowat)
+			io_callback(io, IO_LOWAT);
+	}
+    read:
+
+	if (ev & EV_READ) {
+		iobuf_normalize(&io->iobuf);
+		if ((n = iobuf_read(&io->iobuf, io->sock)) < 0) {
+			if (n == IOBUF_CLOSED)
+				io_callback(io, IO_DISCONNECTED);
+			else {
+				saved_errno = errno;
+				io->error = strerror(errno);
+				errno = saved_errno;
+				io_callback(io, IO_ERROR);
+			}
+			goto leave;
+		}
+		if (n)
+			io_callback(io, IO_DATAIN);
+	}
+
+leave:
+	io_frame_leave(io);
+}
+
+void
+io_callback(struct io *io, int evt)
+{
+	io->cb(io, evt, io->arg);
+}
+
+int
+io_connect(struct io *io, const struct sockaddr *sa, const struct sockaddr *bsa)
+{
+	int	sock, errno_save;
+
+	if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) == -1)
+		goto fail;
+
+	io_set_nonblocking(sock);
+	io_set_nolinger(sock);
+
+	if (bsa && bind(sock, bsa, bsa->sa_len) == -1)
+		goto fail;
+
+	if (connect(sock, sa, sa->sa_len) == -1)
+		if (errno != EINPROGRESS)
+			goto fail;
+
+	io->sock = sock;
+	io_reset(io, EV_WRITE, io_dispatch_connect);
+
+	return (sock);
+
+    fail:
+	if (sock != -1) {
+		errno_save = errno;
+		close(sock);
+		errno = errno_save;
+		io->error = strerror(errno);
+	}
+	return (-1);
+}
+
+void
+io_dispatch_connect(int fd, short ev, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 r, e;
+	socklen_t	 sl;
+
+	io_frame_enter("io_dispatch_connect", io, ev);
+
+	if (ev == EV_TIMEOUT) {
+		close(fd);
+		io->sock = -1;
+		io_callback(io, IO_TIMEOUT);
+	} else {
+		sl = sizeof(e);
+		r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &sl);
+		if (r == -1)  {
+			warn("io_dispatch_connect: getsockopt");
+			e = errno;
+		}
+		if (e) {
+			close(fd);
+			io->sock = -1;
+			io->error = strerror(e);
+			io_callback(io, e == ETIMEDOUT ? IO_TIMEOUT : IO_ERROR);
+		}
+		else {
+			io->state = IO_STATE_UP;
+			io_callback(io, IO_CONNECTED);
+		}
+	}
+
+	io_frame_leave(io);
+}
+
+#ifdef IO_TLS
+
+static const char*
+io_tls_error(void)
+{
+	static char	buf[128];
+	unsigned long	e;
+
+	e = ERR_peek_last_error();
+	if (e) {
+		ERR_error_string(e, buf);
+		return (buf);
+	}
+
+	return ("No TLS error");
+}
+
+int
+io_start_tls(struct io *io, void *tls)
+{
+	int	mode;
+
+	mode = io->flags & IO_RW;
+	if (mode == 0 || mode == IO_RW)
+		errx(1, "io_start_tls(): full-duplex or unset");
+
+	if (io->tls)
+		errx(1, "io_start_tls(): TLS already started");
+	io->tls = tls;
+
+	if (SSL_set_fd(io->tls, io->sock) == 0) {
+		ssl_error("io_start_tls:SSL_set_fd");
+		return (-1);
+	}
+
+	if (mode == IO_WRITE) {
+		io->state = IO_STATE_CONNECT_TLS;
+		SSL_set_connect_state(io->tls);
+		io_reset(io, EV_WRITE, io_dispatch_connect_tls);
+	} else {
+		io->state = IO_STATE_ACCEPT_TLS;
+		SSL_set_accept_state(io->tls);
+		io_reset(io, EV_READ, io_dispatch_accept_tls);
+	}
+
+	return (0);
+}
+
+void
+io_dispatch_accept_tls(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 e, ret;
+
+	io_frame_enter("io_dispatch_accept_tls", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	if ((ret = SSL_accept(io->tls)) > 0) {
+		io->state = IO_STATE_UP;
+		io_callback(io, IO_TLSREADY);
+		goto leave;
+	}
+
+	switch ((e = SSL_get_error(io->tls, ret))) {
+	case SSL_ERROR_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_accept_tls);
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_accept_tls);
+		break;
+	default:
+		io->error = io_tls_error();
+		ssl_error("io_dispatch_accept_tls:SSL_accept");
+		io_callback(io, IO_ERROR);
+		break;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_connect_tls(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 e, ret;
+
+	io_frame_enter("io_dispatch_connect_tls", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	if ((ret = SSL_connect(io->tls)) > 0) {
+		io->state = IO_STATE_UP;
+		io_callback(io, IO_TLSREADY);
+		goto leave;
+	}
+
+	switch ((e = SSL_get_error(io->tls, ret))) {
+	case SSL_ERROR_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_connect_tls);
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_connect_tls);
+		break;
+	default:
+		io->error = io_tls_error();
+		ssl_error("io_dispatch_connect_ssl:SSL_connect");
+		io_callback(io, IO_TLSERROR);
+		break;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_read_tls(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 n, saved_errno;
+
+	io_frame_enter("io_dispatch_read_tls", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+again:
+	iobuf_normalize(&io->iobuf);
+	switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) {
+	case IOBUF_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_read_tls);
+		break;
+	case IOBUF_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_read_tls);
+		break;
+	case IOBUF_CLOSED:
+		io_callback(io, IO_DISCONNECTED);
+		break;
+	case IOBUF_ERROR:
+		saved_errno = errno;
+		io->error = strerror(errno);
+		errno = saved_errno;
+		io_callback(io, IO_ERROR);
+		break;
+	case IOBUF_TLSERROR:
+		io->error = io_tls_error();
+		ssl_error("io_dispatch_read_tls:SSL_read");
+		io_callback(io, IO_ERROR);
+		break;
+	default:
+		io_debug("io_dispatch_read_tls(...) -> r=%d\n", n);
+		io_callback(io, IO_DATAIN);
+		if (current == io && IO_READING(io) && SSL_pending(io->tls))
+			goto again;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_write_tls(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 n, saved_errno;
+	size_t		 w2, w;
+
+	io_frame_enter("io_dispatch_write_tls", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	w = io_queued(io);
+	switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) {
+	case IOBUF_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_write_tls);
+		break;
+	case IOBUF_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_write_tls);
+		break;
+	case IOBUF_CLOSED:
+		io_callback(io, IO_DISCONNECTED);
+		break;
+	case IOBUF_ERROR:
+		saved_errno = errno;
+		io->error = strerror(errno);
+		errno = saved_errno;
+		io_callback(io, IO_ERROR);
+		break;
+	case IOBUF_TLSERROR:
+		io->error = io_tls_error();
+		ssl_error("io_dispatch_write_tls:SSL_write");
+		io_callback(io, IO_ERROR);
+		break;
+	default:
+		io_debug("io_dispatch_write_tls(...) -> w=%d\n", n);
+		w2 = io_queued(io);
+		if (w > io->lowat && w2 <= io->lowat)
+			io_callback(io, IO_LOWAT);
+		break;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_reload_tls(struct io *io)
+{
+	short	ev = 0;
+	void	(*dispatch)(int, short, void*) = NULL;
+
+	switch (io->state) {
+	case IO_STATE_CONNECT_TLS:
+		ev = EV_WRITE;
+		dispatch = io_dispatch_connect_tls;
+		break;
+	case IO_STATE_ACCEPT_TLS:
+		ev = EV_READ;
+		dispatch = io_dispatch_accept_tls;
+		break;
+	case IO_STATE_UP:
+		ev = 0;
+		if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) {
+			ev = EV_READ;
+			dispatch = io_dispatch_read_tls;
+		}
+		else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) &&
+		    io_queued(io)) {
+			ev = EV_WRITE;
+			dispatch = io_dispatch_write_tls;
+		}
+		if (!ev)
+			return; /* paused */
+		break;
+	default:
+		errx(1, "io_reload_tls(): bad state");
+	}
+
+	io_reset(io, ev, dispatch);
+}
+
+#endif /* IO_TLS */
blob - /dev/null
blob + 015340c2b1664d79c58e83c288b59689a8d97ac1 (mode 644)
--- /dev/null
+++ ioev.h
@@ -0,0 +1,69 @@
+/*	$OpenBSD: ioev.h,v 1.17 2019/06/12 17:42:53 eric Exp $	*/
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+enum {
+	IO_CONNECTED = 0, 	/* connection successful	*/
+	IO_TLSREADY,		/* TLS started successfully	*/
+	IO_TLSERROR,		/* XXX - needs more work	*/
+	IO_DATAIN,		/* new data in input buffer	*/
+	IO_LOWAT,		/* output queue running low	*/
+	IO_DISCONNECTED,	/* error?			*/
+	IO_TIMEOUT,		/* error?			*/
+	IO_ERROR,		/* details?			*/
+};
+
+#define IO_IN		0x01
+#define IO_OUT		0x02
+
+struct io;
+
+void io_set_nonblocking(int);
+void io_set_nolinger(int);
+
+struct io *io_new(void);
+void io_free(struct io *);
+void io_set_read(struct io *);
+void io_set_write(struct io *);
+void io_set_fd(struct io *, int);
+void io_set_callback(struct io *io, void(*)(struct io *, int, void *), void *);
+void io_set_timeout(struct io *, int);
+void io_set_lowat(struct io *, size_t);
+void io_pause(struct io *, int);
+void io_resume(struct io *, int);
+void io_reload(struct io *);
+int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *);
+int io_start_tls(struct io *, void *);
+const char* io_strio(struct io *);
+const char* io_strevent(int);
+const char* io_error(struct io *);
+void* io_tls(struct io *);
+int io_fileno(struct io *);
+int io_paused(struct io *, int);
+
+/* Buffered output functions */
+int io_write(struct io *, const void *, size_t);
+int io_writev(struct io *, const struct iovec *, int);
+int io_print(struct io *, const char *);
+int io_printf(struct io *, const char *, ...);
+int io_vprintf(struct io *, const char *, va_list);
+size_t io_queued(struct io *);
+
+/* Buffered input functions */
+void* io_data(struct io *);
+size_t io_datalen(struct io *);
+char* io_getline(struct io *, size_t *);
+void io_drop(struct io *, size_t);
blob - /dev/null
blob + 76e526d31261c26e5d4088450acdf143432e8c08 (mode 644)
--- /dev/null
+++ opensmtpd.c
@@ -0,0 +1,1291 @@
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/time.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "opensmtpd.h"
+#include "ioev.h"
+#include "opensmtpd_priv.h"
+
+#define NITEMS(x) (sizeof(x) / sizeof(*x))
+
+static struct io *io_stdout;
+
+RB_HEAD(osmtpd_sessions, osmtpd_session) osmtpd_sessions = RB_INITIALIZER(NULL);
+RB_PROTOTYPE(osmtpd_sessions, osmtpd_session, entry, osmtpd_session_cmp);
+
+static int ready = 0;
+/* Default from smtpd */
+static int session_timeout = 300;
+
+void
+osmtpd_register_filter_connect(void (*cb)(struct osmtpd_ctx *, const char *,
+    struct sockaddr_storage *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_CONNECT, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_helo(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_HELO, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_ehlo(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_EHLO, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_starttls(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_STARTTLS, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_auth(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_AUTH, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_mailfrom(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_MAIL_FROM, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_rcptto(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_RCPT_TO, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_data(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_DATA, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_dataline(void (*cb)(struct osmtpd_ctx *, const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_DATA_LINE, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_rset(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_RSET, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_quit(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_QUIT, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_noop(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_NOOP, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_help(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_HELP, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_wiz(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_WIZ, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_filter_commit(void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_COMMIT, 1, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT, 1, 0,
+	    NULL);
+}
+
+void
+osmtpd_register_report_connect(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *, const char *, struct sockaddr_storage *,
+    struct sockaddr_storage *))
+{
+	osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_LINK_CONNECT, incoming,
+	    0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_disconnect(int incoming, void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, (void *)cb);
+}
+
+void
+osmtpd_register_report_identify(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_IDENTIFY,
+	    incoming, 0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_tls(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_TLS, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_begin(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_BEGIN, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_mail(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t, const char *, enum osmtpd_status))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_MAIL, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_rcpt(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t, const char *, enum osmtpd_status))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_RCPT, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_envelope(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t, uint64_t))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ENVELOPE, incoming,
+	    0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_data(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t, enum osmtpd_status))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_DATA, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_commit(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t, size_t))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_COMMIT, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_rollback(int incoming, void (*cb)(struct osmtpd_ctx *,
+    uint32_t))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ROLLBACK, incoming,
+	    0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_client(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_PROTOCOL_CLIENT,
+	    incoming, 0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_server(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_PROTOCOL_SERVER,
+	    incoming, 0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_response(int incoming, void (*cb)(struct osmtpd_ctx *,
+    const char *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_FILTER_RESPONSE,
+	    incoming, 0, (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_register_report_timeout(int incoming, void (*cb)(struct osmtpd_ctx *))
+{
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TIMEOUT, incoming, 0,
+	    (void *)cb);
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_localdata(void *(*oncreate)(struct osmtpd_ctx *),
+    void (*ondelete)(struct osmtpd_ctx *, void *))
+{
+	oncreatecb = oncreate;
+	ondeletecb = ondelete;
+}
+
+void
+osmtpd_need(int incoming, int needs)
+{
+	if (needs & (OSMTPD_NEED_SRC | OSMTPD_NEED_DST | OSMTPD_NEED_RDNS |
+	    OSMTPD_NEED_FRDNS))
+		osmtpd_register(OSMTPD_TYPE_FILTER, OSMTPD_PHASE_LINK_CONNECT,
+		    incoming, 1, NULL);
+	if (needs & OSMTPD_NEED_IDENTITY)
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_IDENTIFY,
+		    incoming, 1, NULL);
+	if (needs & OSMTPD_NEED_CIPHERS)
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_TLS,
+		    incoming, 1, NULL);
+	if (needs & OSMTPD_NEED_MSGID) {
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_BEGIN,
+		    incoming, 1, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ROLLBACK,
+		    incoming, 0, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_COMMIT,
+		    incoming, 0, NULL);
+	}
+	if (needs & OSMTPD_NEED_MAILFROM) {
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_MAIL,
+		    incoming, 1, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ROLLBACK,
+		    incoming, 0, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_COMMIT,
+		    incoming, 0, NULL);
+	}
+	if (needs & OSMTPD_NEED_RCPTTO) {
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_RCPT,
+		    incoming, 1, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ROLLBACK,
+		    incoming, 0, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_COMMIT,
+		    incoming, 0, NULL);
+	}
+	if (needs & OSMTPD_NEED_EVPID) {
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ENVELOPE,
+		    incoming, 1, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_ROLLBACK,
+		    incoming, 0, NULL);
+		osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_TX_COMMIT,
+		    incoming, 0, NULL);
+	}
+
+	osmtpd_register(OSMTPD_TYPE_REPORT, OSMTPD_PHASE_LINK_DISCONNECT,
+	    incoming, 0, NULL);
+}
+
+void
+osmtpd_run(void)
+{
+	size_t i = 0;
+	int registered = 0;
+	/* Doesn't leak, since it needs to exists until the application ends. */
+	struct io *io_stdin;
+
+	event_init();
+
+	if ((io_stdin = io_new()) == NULL ||
+	    (io_stdout = io_new()) == NULL)
+		err(1, "io_new");
+	io_set_nonblocking(STDIN_FILENO);
+	io_set_fd(io_stdin, STDIN_FILENO);
+	io_set_callback(io_stdin, osmtpd_newline, NULL);
+	io_set_read(io_stdin);
+	io_set_nonblocking(STDOUT_FILENO);
+	io_set_fd(io_stdout, STDOUT_FILENO);
+	io_set_callback(io_stdout, osmtpd_outevt, NULL);
+	io_set_write(io_stdout);
+
+	for (i = 0; i < NITEMS(osmtpd_callbacks); i++) {
+		if (osmtpd_callbacks[i].doregister) {
+			if (osmtpd_callbacks[i].cb != NULL)
+				registered = 1;
+			io_printf(io_stdout, "register|%s|smtp-%s|%s\n",
+			    osmtpd_typetostr(osmtpd_callbacks[i].type),
+			    osmtpd_callbacks[i].incoming ? "in" : "out",
+			    osmtpd_phasetostr(osmtpd_callbacks[i].phase));
+		}
+	}
+
+	if (!registered)
+		errx(1, "No events registered");
+	io_printf(io_stdout, "register|ready\n");
+	ready = 1;
+
+	event_dispatch();
+}
+
+static void
+osmtpd_newline(struct io *io, int ev, void *arg)
+{
+	static char *linedup = NULL;
+	static size_t dupsize = 0;
+	struct osmtpd_session *ctx, search;
+	enum osmtpd_type type;
+	enum osmtpd_phase phase;
+	int version_major, version_minor, incoming;
+	struct timespec tm;
+	char *line = NULL;
+	const char *errstr = NULL;
+	size_t linelen;
+	char *end;
+	size_t i;
+
+	if (ev == IO_DISCONNECTED)
+		exit(0);
+	if (ev != IO_DATAIN)
+		return;
+	while ((line = io_getline(io, &linelen)) > 0) {
+		if (dupsize < linelen) {
+			if ((linedup = realloc(linedup, linelen + 1)) == NULL)
+				err(1, NULL);
+			dupsize = linelen + 1;
+		}
+		strlcpy(linedup, line, dupsize);
+		if ((end = strchr(line, '|')) == NULL)
+			errx(1, "Invalid line received: missing version: %s", linedup);
+		end++[0] = '\0';
+		if (strcmp(line, "filter") == 0)
+			type = OSMTPD_TYPE_FILTER;
+		else if (strcmp(line, "report") == 0)
+			type = OSMTPD_TYPE_REPORT;
+		else if (strcmp(line, "config") == 0) {
+			line = end;
+			if (strcmp(line, "ready") == 0)
+				continue;
+			if ((end = strchr(line, '|')) == NULL)
+				errx(1, "Invalid line received: missing key: %s", linedup);
+			end++[0] = '\0';
+			if (strcmp(line, "smtp-session-timeout") == 0) {
+				session_timeout = strtonum(end, 0, INT_MAX,
+				    &errstr);
+				if (errstr != NULL)
+					errx(1, "Invalid line received: "
+					    "invalid smtp-sesion-timeout: %s",
+					    linedup);
+			}
+			continue;
+		}
+		else
+			errx(1, "Invalid line received: unknown message type: %s", linedup);
+		line = end;
+		if ((end = strchr(line, '|')) == NULL)
+			errx(1, "Invalid line received: missing time: %s", linedup);
+		end++[0] = '\0';
+		if (strcmp(line, "0.1") != 0)
+			errx(1, "Unsupported protocol received: %s", linedup);
+		version_major = 0;
+		version_minor = 1;
+		line = end;
+		if ((end = strchr(line, '.')) == NULL)
+			errx(1, "Invalid line received: invalid timestamp: %s", linedup);
+		end++[0] = '\0';
+		tm.tv_sec = (time_t) strtonum(line, 0, INT64_MAX, &errstr);
+		if (errstr != NULL)
+			errx(1, "Invalid line received: invalid timestamp: %s", linedup);
+		line = end;
+		if ((end = strchr(line, '|')) == NULL)
+			errx(1, "Invalid line received: missing direction: %s", linedup);
+		end++[0] = '\0';
+		tm.tv_nsec = (long) strtonum(line, 0, LONG_MAX, &errstr);
+		if (errstr != NULL)
+			errx(1, "Invalid line received: invalid timestamp: %s",
+			    linedup);
+		tm.tv_nsec *= 10 * (9 - (end - line));
+		line = end;
+		if ((end = strchr(line, '|')) == NULL)
+			errx(1, "Invalid line receieved: missing phase: %s",
+			    linedup);
+		end++[0] = '\0';
+		if (strcmp(line, "smtp-in") == 0)
+			incoming = 1;
+		else if (strcmp(line, "smtp-out") == 0)
+			incoming = 0;
+		else
+			errx(1, "Invalid line: invalid direction: %s", linedup);
+		line = end;
+		if ((end = strchr(line, '|')) == NULL)
+			errx(1, "Invalid line received: missing reqid: %s",
+			    linedup);
+		end++[0] = '\0';
+		phase = osmtpd_strtophase(line, linedup);
+		line = end;
+		errno = 0;
+		search.ctx.reqid = strtoull(line, &end, 16);
+		if ((search.ctx.reqid == ULLONG_MAX && errno != 0) ||
+		    (end[0] != '|' && end[0] != '\0'))
+			errx(1, "Invalid line received: invalid reqid: %s",
+			    linedup);
+		line = end + 1;
+		ctx = RB_FIND(osmtpd_sessions, &osmtpd_sessions, &search);
+		if (ctx == NULL) {
+			if ((ctx = malloc(sizeof(*ctx))) == NULL)
+				err(1, NULL);
+			ctx->ctx.reqid = search.ctx.reqid;
+			ctx->ctx.rdns = NULL;
+			ctx->ctx.fcrdns = NULL;
+			ctx->ctx.identity = NULL;
+			ctx->ctx.ciphers = NULL;
+			ctx->ctx.msgid = 0;
+			ctx->ctx.mailfrom = NULL;
+			ctx->ctx.rcptto = malloc(sizeof(*(ctx->ctx.rcptto)));
+			if (ctx->ctx.rcptto == NULL)
+				err(1, "malloc");
+			ctx->ctx.rcptto[0] = NULL;
+			memset(&(ctx->ctx.src), 0, sizeof(ctx->ctx.src));
+			ctx->ctx.src.ss_family = AF_UNSPEC;
+			memset(&(ctx->ctx.dst), 0, sizeof(ctx->ctx.dst));
+			ctx->ctx.dst.ss_family = AF_UNSPEC;
+			RB_INSERT(osmtpd_sessions, &osmtpd_sessions, ctx);
+			ctx->ctx.evpid = 0;
+			ctx->ctx.local = NULL;
+			if (oncreatecb != NULL)
+				ctx->ctx.local = oncreatecb(&ctx->ctx);
+		}
+		ctx->ctx.type = type;
+		ctx->ctx.phase = phase;
+		ctx->ctx.version_major = version_major;
+		ctx->ctx.version_minor = version_minor;
+		ctx->ctx.incoming = incoming;
+		ctx->ctx.tm.tv_sec = tm.tv_sec;
+		ctx->ctx.tm.tv_nsec = tm.tv_nsec;
+		ctx->ctx.token = 0;
+
+		for (i = 0; i < NITEMS(osmtpd_callbacks); i++) {
+			if (ctx->ctx.type == osmtpd_callbacks[i].type &&
+			    ctx->ctx.phase == osmtpd_callbacks[i].phase &&
+			    ctx->ctx.incoming == osmtpd_callbacks[i].incoming)
+				break;
+		}
+		if (i == NITEMS(osmtpd_callbacks)) {
+			errx(1, "Invalid line received: received unregistered "
+			    "line: %s", linedup);
+		}
+		if (ctx->ctx.type == OSMTPD_TYPE_FILTER) {
+			ctx->ctx.token = strtoull(line, &end, 16);
+			if ((ctx->ctx.token == ULLONG_MAX && errno != 0) || 
+			    end[0] != '|')
+				errx(1, "Invalid line received: invalid token: %s", linedup);
+			line = end + 1;
+		}
+		osmtpd_callbacks[i].osmtpd_cb(&(osmtpd_callbacks[i]),
+		    &(ctx->ctx), line, linedup);
+	}
+}
+
+static void
+osmtpd_outevt(struct io *io, int evt, void *arg)
+{
+	switch (evt) {
+	case IO_LOWAT:
+		return;
+	case IO_DISCONNECTED:
+		exit(0);
+	default:
+		errx(1, "Unexpectd event");
+	}
+}
+
+static void
+osmtpd_noargs(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx, char *params,
+    char *linedup)
+{
+	void (*f)(struct osmtpd_ctx *);
+
+	f = cb->cb;
+	f(ctx);
+}
+
+static void
+osmtpd_onearg(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx, char *line,
+    char *linedup)
+{
+	void (*f)(struct osmtpd_ctx *, const char *);
+
+	f = cb->cb;
+	f(ctx, line);
+}
+
+static void
+osmtpd_connect(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx, char *params,
+    char *linedup)
+{
+	struct sockaddr_storage ss;
+	char *hostname;
+	char *address;
+	void (*f)(struct osmtpd_ctx *, const char *, struct sockaddr_storage *);
+
+	hostname = params;
+	if ((address = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	address++[0] = '\0';
+
+	osmtpd_addrtoss(address, &ss, 0, linedup);
+
+	f = cb->cb;
+	f(ctx, hostname, &ss);
+}
+
+static void
+osmtpd_link_connect(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end, *rdns, *fcrdns;
+	struct sockaddr_storage src, dst;
+	void (*f)(struct osmtpd_ctx *, const char *, const char *,
+	     struct sockaddr_storage *, struct sockaddr_storage *);
+
+	if ((end = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing fcrdns: %s", linedup);
+	end++[0] = '\0';
+	rdns = params;
+	params = end;
+	if ((end = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing src: %s", linedup);
+	end++[0] = '\0';
+	fcrdns = params;
+	params = end;
+	if ((end = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing dst: %s", linedup);
+	end++[0] = '\0';
+	osmtpd_addrtoss(params, &src, 1, linedup);
+	params = end;
+	osmtpd_addrtoss(params, &dst, 1, linedup);
+	if (cb->storereport) {
+		if ((ctx->rdns = strdup(params)) == NULL)
+			err(1, NULL);
+		if ((ctx->fcrdns = strdup(params)) == NULL)
+			err(1, NULL);
+		memcpy(&(ctx->src), &src, sizeof(ctx->src));
+		memcpy(&(ctx->dst), &dst, sizeof(ctx->dst));
+	}
+	if ((f = cb->cb) != NULL)
+		f(ctx, rdns, fcrdns, &src, &dst);
+}
+
+static void
+osmtpd_link_disconnect(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *param, char *linedup)
+{
+	void (*f)(struct osmtpd_ctx *);
+	size_t i;
+	struct osmtpd_session *session, search;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx);
+
+	search.ctx.reqid = ctx->reqid;
+	session = RB_FIND(osmtpd_sessions, &osmtpd_sessions, &search);
+	if (session != NULL) {
+		RB_REMOVE(osmtpd_sessions, &osmtpd_sessions, session);
+		if (ondeletecb != NULL)
+			ondeletecb(ctx, session->ctx.local);
+		free(session->ctx.rdns);
+		free(session->ctx.fcrdns);
+		free(session->ctx.identity);
+		free(session->ctx.ciphers);
+		free(session->ctx.mailfrom);
+		for (i = 0; session->ctx.rcptto[i] != NULL; i++)
+			free(session->ctx.rcptto[i]);
+		free(session->ctx.rcptto);
+		free(session);
+	}
+}
+
+static void
+osmtpd_link_identify(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *identity, char *linedup)
+{
+	void (*f)(struct osmtpd_ctx *, const char *);
+
+	if (cb->storereport) {
+		if ((ctx->identity = strdup(identity)) == NULL)
+			err(1, NULL);
+	}
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, identity);
+}
+
+static void
+osmtpd_link_tls(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *ciphers, char *linedup)
+{
+	void (*f)(struct osmtpd_ctx *, const char *);
+
+	if (cb->storereport) {
+		if ((ctx->ciphers = strdup(ciphers)) == NULL)
+			err(1, NULL);
+	}
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, ciphers);
+}
+
+static void
+osmtpd_tx_begin(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *msgid, char *linedup)
+{
+	unsigned long imsgid;
+	char *endptr;
+	void (*f)(struct osmtpd_ctx *, uint32_t);
+
+	errno = 0;
+	imsgid = strtoul(msgid, &endptr, 16);
+	if ((imsgid == ULONG_MAX && errno != 0) || endptr[0] != '\0')
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	ctx->msgid = imsgid;
+	/* Check if we're in range */
+	if ((unsigned long) ctx->msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+
+	if (!cb->storereport)
+		ctx->msgid = 0;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, imsgid);
+}
+
+static void
+osmtpd_tx_mail(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end, *mailfrom;
+	unsigned long imsgid;
+	uint32_t msgid;
+	void (*f)(struct osmtpd_ctx *, uint32_t, const char *,
+	    enum osmtpd_status);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '|')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	params = end + 1;
+
+	if ((end = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing status: %s", linedup);
+	end++[0] = '\0';
+	mailfrom = params;
+	if (cb->storereport) {
+		if ((ctx->mailfrom = strdup(mailfrom)) == NULL)
+			err(1, NULL);
+	}
+	params = end;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid, ctx->mailfrom,
+		    osmtpd_strtostatus(params, linedup));
+}
+
+static void
+osmtpd_tx_rcpt(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end, *rcptto;
+	unsigned long imsgid;
+	uint32_t msgid;
+	size_t i;
+	void (*f)(struct osmtpd_ctx *, uint32_t, const char *,
+	    enum osmtpd_status);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '|')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	params = end + 1;
+
+	if ((end = strchr(params, '|')) == NULL)
+		errx(1, "Invalid line received: missing status: %s", linedup);
+	end++[0] = '\0';
+
+	rcptto = params;
+	params = end;
+
+	if (cb->storereport) {
+		for (i = 0; ctx->rcptto[i] != NULL; i++)
+			;
+		ctx->rcptto = reallocarray(ctx->rcptto, i + 2,
+		    sizeof(*(ctx->rcptto)));
+		if (ctx->rcptto == NULL)
+			err(1, NULL);
+
+		if ((ctx->rcptto[i] = strdup(rcptto)) == NULL)
+			err(1, NULL);
+		ctx->rcptto[i + 1] = NULL;
+	}
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid, rcptto,
+		    osmtpd_strtostatus(params, linedup));
+}
+
+static void
+osmtpd_tx_envelope(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	unsigned long imsgid;
+	uint32_t msgid;
+	uint64_t evpid;
+	char *end;
+	void (*f)(struct osmtpd_ctx *, uint32_t, uint64_t);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '|')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	params = end + 1;
+
+	evpid = strtoull(params, &end, 16);
+	if ((ctx->evpid == ULLONG_MAX && errno != 0) || 
+	    end[0] != '\0')
+		errx(1, "Invalid line received: invalid evpid: %s", linedup);
+	if (cb->storereport)
+		ctx->evpid = evpid;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid, evpid);
+}
+
+static void
+osmtpd_tx_data(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end;
+	unsigned long imsgid;
+	uint32_t msgid;
+	void (*f)(struct osmtpd_ctx *, uint32_t, enum osmtpd_status);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '|')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	params = end + 1;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid, osmtpd_strtostatus(params, linedup));
+}
+
+static void
+osmtpd_tx_commit(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end;
+	const char *errstr;
+	unsigned long imsgid;
+	uint32_t msgid;
+	size_t i, msgsz;
+	void (*f)(struct osmtpd_ctx *, uint32_t, size_t);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '|')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	params = end + 1;
+
+	msgsz = strtonum(params, 0, SIZE_MAX, &errstr);
+	if (errstr != NULL)
+		errx(1, "Invalid line received: invalid msg size: %s", linedup);
+
+	free(ctx->mailfrom);
+	ctx->mailfrom = NULL;
+
+	for (i = 0; ctx->rcptto[i] != NULL; i++)
+		free(ctx->rcptto[i]);
+	ctx->rcptto[0] = NULL;
+	ctx->evpid = 0;
+	ctx->msgid = 0;
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid, msgsz);
+}
+
+static void
+osmtpd_tx_rollback(struct osmtpd_callback *cb, struct osmtpd_ctx *ctx,
+    char *params, char *linedup)
+{
+	char *end;
+	unsigned long imsgid;
+	uint32_t msgid;
+	size_t i;
+	void (*f)(struct osmtpd_ctx *, uint32_t);
+
+	errno = 0;
+	imsgid = strtoul(params, &end, 16);
+	if ((imsgid == ULONG_MAX && errno != 0))
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+	if (end[0] != '\0')
+		errx(1, "Invalid line received: missing address: %s", linedup);
+	msgid = imsgid;
+	if ((unsigned long) msgid != imsgid)
+		errx(1, "Invalid line received: invalid msgid: %s", linedup);
+
+	if ((f = cb->cb) != NULL)
+		f(ctx, msgid);
+
+	free(ctx->mailfrom);
+	ctx->mailfrom = NULL;
+
+	for (i = 0; ctx->rcptto[i] != NULL; i++)
+		free(ctx->rcptto[i]);
+	ctx->rcptto[0] = NULL;
+	ctx->evpid = 0;
+	ctx->msgid = 0;
+}
+
+void
+osmtpd_filter_proceed(struct osmtpd_ctx *ctx)
+{
+	io_printf(io_stdout, "filter-result|%016"PRIx64"|%016"PRIx64"|"
+	    "proceed\n", ctx->token, ctx->reqid);
+}
+
+void
+osmtpd_filter_reject(struct osmtpd_ctx *ctx, int code, const char *reason, ...)
+{
+	va_list ap;
+
+	if (code < 200 || code > 599)
+		errx(1, "Invalid reject code");
+
+	io_printf(io_stdout, "filter-result|%016"PRIx64"|%016"PRIx64"|reject|"
+	    "%d ", ctx->token, ctx->reqid, code);
+	va_start(ap, reason);
+	io_vprintf(io_stdout, reason, ap);
+	va_end(ap);
+	io_printf(io_stdout, "\n");
+}
+
+void
+osmtpd_filter_disconnect(struct osmtpd_ctx *ctx, const char *reason, ...)
+{
+	va_list ap;
+
+	io_printf(io_stdout, "filter-result|%016"PRIx64"|%016"PRIx64"|"
+	    "disconnect|421 ", ctx->token, ctx->reqid);
+	va_start(ap, reason);
+	io_vprintf(io_stdout, reason, ap);
+	va_end(ap);
+	io_printf(io_stdout, "\n");
+}
+
+void
+osmtpd_filter_dataline(struct osmtpd_ctx *ctx, const char *line, ...)
+{
+	va_list ap;
+
+	io_printf(io_stdout, "filter-dataline|%016"PRIx64"|%016"PRIx64"|",
+	    ctx->token, ctx->reqid);
+	va_start(ap, line);
+	io_vprintf(io_stdout, line, ap);
+	va_end(ap);
+	io_printf(io_stdout, "\n");
+}
+
+static void
+osmtpd_register(enum osmtpd_type type, enum osmtpd_phase phase, int incoming,
+    int storereport, void *cb)
+{
+	size_t i;
+
+	if (ready)
+		errx(1, "Can't register when proc is running");
+
+	for (i = 0; i < NITEMS(osmtpd_callbacks); i++) {
+		if (type ==  osmtpd_callbacks[i].type &&
+		    phase == osmtpd_callbacks[i].phase &&
+		    incoming == osmtpd_callbacks[i].incoming) {
+			if (osmtpd_callbacks[i].cb != NULL && cb != NULL)
+				errx(1, "Event already registered");
+			osmtpd_callbacks[i].cb = cb;
+			osmtpd_callbacks[i].doregister = 1;
+			if (storereport)
+				osmtpd_callbacks[i].storereport = 1;
+			return;
+		}
+	}
+	/* NOT REACHED */
+	errx(1, "Trying to register unknown event");
+}
+
+static enum osmtpd_phase
+osmtpd_strtophase(const char *phase, const char *linedup)
+{
+	if (strcmp(phase, "connect") == 0)
+		return OSMTPD_PHASE_CONNECT;
+	if (strcmp(phase, "helo") == 0)
+		return OSMTPD_PHASE_HELO;
+	if (strcmp(phase, "ehlo") == 0)
+		return OSMTPD_PHASE_EHLO;
+	if (strcmp(phase, "starttls") == 0)
+		return OSMTPD_PHASE_STARTTLS;
+	if (strcmp(phase, "auth") == 0)
+		return OSMTPD_PHASE_AUTH;
+	if (strcmp(phase, "mail-from") == 0)
+		return OSMTPD_PHASE_MAIL_FROM;
+	if (strcmp(phase, "rcpt-to") == 0)
+		return OSMTPD_PHASE_RCPT_TO;
+	if (strcmp(phase, "data") == 0)
+		return OSMTPD_PHASE_DATA;
+	if (strcmp(phase, "data-line") == 0)
+		return OSMTPD_PHASE_DATA_LINE;
+	if (strcmp(phase, "rset") == 0)
+		return OSMTPD_PHASE_RSET;
+	if (strcmp(phase, "quit") == 0)
+		return OSMTPD_PHASE_QUIT;
+	if (strcmp(phase, "noop") == 0)
+		return OSMTPD_PHASE_NOOP;
+	if (strcmp(phase, "help") == 0)
+		return OSMTPD_PHASE_HELP;
+	if (strcmp(phase, "wiz") == 0)
+		return OSMTPD_PHASE_WIZ;
+	if (strcmp(phase, "commit") == 0)
+		return OSMTPD_PHASE_COMMIT;
+	if (strcmp(phase, "link-connect") == 0)
+		return OSMTPD_PHASE_LINK_CONNECT;
+	if (strcmp(phase, "link-disconnect") == 0)
+		return OSMTPD_PHASE_LINK_DISCONNECT;
+	if (strcmp(phase, "link-identify") == 0)
+		return OSMTPD_PHASE_LINK_IDENTIFY;
+	if (strcmp(phase, "link-tls") == 0)
+		return OSMTPD_PHASE_LINK_TLS;
+	if (strcmp(phase, "tx-begin") == 0)
+		return OSMTPD_PHASE_TX_BEGIN;
+	if (strcmp(phase, "tx-mail") == 0)
+		return OSMTPD_PHASE_TX_MAIL;
+	if (strcmp(phase, "tx-rcpt") == 0)
+		return OSMTPD_PHASE_TX_RCPT;
+	if (strcmp(phase, "tx-envelope") == 0)
+		return OSMTPD_PHASE_TX_ENVELOPE;
+	if (strcmp(phase, "tx-data") == 0)
+		return OSMTPD_PHASE_TX_DATA;
+	if (strcmp(phase, "tx-commit") == 0)
+		return OSMTPD_PHASE_TX_COMMIT;
+	if (strcmp(phase, "tx-rollback") == 0)
+		return OSMTPD_PHASE_TX_ROLLBACK;
+	if (strcmp(phase, "protocol-client") == 0)
+		return OSMTPD_PHASE_PROTOCOL_CLIENT;
+	if (strcmp(phase, "protocol-server") == 0)
+		return OSMTPD_PHASE_PROTOCOL_SERVER;
+	if (strcmp(phase, "filter-response") == 0)
+		return OSMTPD_PHASE_FILTER_RESPONSE;
+	if (strcmp(phase, "timeout") == 0)
+		return OSMTPD_PHASE_TIMEOUT;
+	errx(1, "Invalid line received: invalid phase: %s", linedup);
+}
+
+static const char *
+osmtpd_typetostr(enum osmtpd_type type)
+{
+	switch (type) {
+	case OSMTPD_TYPE_FILTER:
+		return "filter";
+	case OSMTPD_TYPE_REPORT:
+		return "report";
+	}
+	errx(1, "In valid type: %d\n", type);
+}
+
+static const char *
+osmtpd_phasetostr(enum osmtpd_phase phase)
+{
+	switch (phase) {
+	case OSMTPD_PHASE_CONNECT:
+		return "connect";
+	case OSMTPD_PHASE_HELO:
+		return "helo";
+	case OSMTPD_PHASE_EHLO:
+		return "ehlo";
+	case OSMTPD_PHASE_STARTTLS:
+		return "starttls";
+	case OSMTPD_PHASE_AUTH:
+		return "auth";
+	case OSMTPD_PHASE_MAIL_FROM:
+		return "mail-from";
+	case OSMTPD_PHASE_RCPT_TO:
+		return "rcpt-to";
+	case OSMTPD_PHASE_DATA:
+		return "data";
+	case OSMTPD_PHASE_DATA_LINE:
+		return "data-line";
+	case OSMTPD_PHASE_RSET:
+		return "rset";
+	case OSMTPD_PHASE_QUIT:
+		return "quit";
+	case OSMTPD_PHASE_NOOP:
+		return "noop";
+	case OSMTPD_PHASE_HELP:
+		return "help";
+	case OSMTPD_PHASE_WIZ:
+		return "wiz";
+	case OSMTPD_PHASE_COMMIT:
+		return "commit";
+	case OSMTPD_PHASE_LINK_CONNECT:
+		return "link-connect";
+	case OSMTPD_PHASE_LINK_DISCONNECT:
+		return "link-disconnect";
+	case OSMTPD_PHASE_LINK_IDENTIFY:
+		return "link-identify";
+	case OSMTPD_PHASE_LINK_TLS:
+		return "link-tls";
+	case OSMTPD_PHASE_TX_BEGIN:
+		return "tx-begin";
+	case OSMTPD_PHASE_TX_MAIL:
+		return "tx-mail";
+	case OSMTPD_PHASE_TX_RCPT:
+		return "tx-rcpt";
+	case OSMTPD_PHASE_TX_ENVELOPE:
+		return "tx-envelope";
+	case OSMTPD_PHASE_TX_DATA:
+		return "tx-data";
+	case OSMTPD_PHASE_TX_COMMIT:
+		return "tx-commit";
+	case OSMTPD_PHASE_TX_ROLLBACK:
+		return "tx-rollback";
+	case OSMTPD_PHASE_PROTOCOL_CLIENT:
+		return "protocol-client";
+	case OSMTPD_PHASE_PROTOCOL_SERVER:
+		return "protocol-server";
+	case OSMTPD_PHASE_FILTER_RESPONSE:
+		return "filter-response";
+	case OSMTPD_PHASE_TIMEOUT:
+		return "timeout";
+	}
+	errx(1, "In valid phase: %d\n", phase);
+}
+
+static enum
+osmtpd_status osmtpd_strtostatus(const char *status, char *linedup)
+{
+	if (strcmp(status, "ok") == 0)
+		return OSMTPD_STATUS_OK;
+	else if (strcmp(status, "tempfail") == 0)
+		return OSMTPD_STATUS_TEMPFAIL;
+	else if (strcmp(status, "permfail") == 0)
+		return OSMTPD_STATUS_PERMFAIL;
+	errx(1, "Invalid line received: invalid status: %s\n", linedup);
+}
+
+static void
+osmtpd_addrtoss(char *addr, struct sockaddr_storage *ss, int hasport,
+    char *linedup)
+{
+	char *port;
+	const char *errstr = NULL;
+	struct sockaddr_in *sin;
+	struct sockaddr_in6 *sin6;
+	struct sockaddr_un *sun;
+
+	if (strncasecmp(addr, "ipv6:", 5) == 0) {
+		sin6 = (struct sockaddr_in6 *)ss;
+		sin6->sin6_len = sizeof(*sin6);
+		sin6->sin6_family = AF_INET6;
+		sin6->sin6_port = 0;
+		if (hasport) {
+			if ((port = strrchr(addr, ':')) == NULL)
+				errx(1, "Invalid line received: invalid "
+				    "address (%s): %s", addr, linedup);
+			port++;
+			sin6->sin6_port = htons(strtonum(port, 0, UINT16_MAX,
+			    &errstr));
+			if (errstr != NULL)
+				errx(1, "Invalid line received: invalid "
+				    "address (%s): %s", addr, linedup);
+			port[-1] = '\0';
+		}
+		switch (inet_pton(AF_INET6, addr + 5, &(sin6->sin6_addr))) {
+		case 1:
+			break;
+		case 0:
+			port[-1] = ':';
+			errx(1, "Invalid line received: invalid address "
+			    "(%s): %s", addr, linedup);
+		default:
+			port[-1] = ':';
+			err(1, "Can't parse address (%s): %s", addr, linedup);
+		}
+	} else if (strncasecmp(addr, "unix:", 5) == 0) {
+		sun = (struct sockaddr_un *)ss;
+		sun->sun_len = sizeof(*sun);
+		sun->sun_family = AF_UNIX;
+		if (strlcpy(sun->sun_path, addr,
+		    sizeof(sun->sun_path)) >= sizeof(sun->sun_path)) {
+			errx(1, "Invalid line received: address too long (%s): "
+			    "%s", addr, linedup);
+		}
+	} else {
+		sin = (struct sockaddr_in *)ss;
+		sin->sin_len = sizeof(*sin);
+		sin->sin_family = AF_INET;
+		sin->sin_port = 0;
+		if (hasport) {
+			if ((port = strrchr(addr, ':')) == NULL)
+				errx(1, "Invalid line received: invalid "
+				    "address (%s): %s", addr, linedup);
+			port++;
+			sin->sin_port = htons(strtonum(port, 0, UINT16_MAX,
+			    &errstr));
+			if (errstr != NULL)
+				errx(1, "Invalid line received: invalid "
+				    "address (%s): %s", addr, linedup);
+			port[-1] = '\0';
+		}
+		switch (inet_pton(AF_INET, addr, &(sin->sin_addr))) {
+		case 1:
+			break;
+		case 0:
+			port[-1] = ':';
+			errx(1, "Invalid line received: invalid address "
+			    "(%s): %s", addr, linedup);
+		default:
+			port[-1] = ':';
+			err(1, "Can't parse address (%s): %s", addr, linedup);
+		}
+	}
+}
+
+static int
+osmtpd_session_cmp(struct osmtpd_session *a, struct osmtpd_session *b)
+{
+	return a->ctx.reqid < b->ctx.reqid ? -1 : a->ctx.reqid > b->ctx.reqid;
+}
+
+RB_GENERATE(osmtpd_sessions, osmtpd_session, entry, osmtpd_session_cmp);
blob - /dev/null
blob + 5117366b3b49a555d29847867705bd42459a06c9 (mode 644)
--- /dev/null
+++ opensmtpd.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <netinet/in.h>
+
+#include <netinet/in.h>
+
+enum osmtpd_status {
+	OSMTPD_STATUS_OK,
+	OSMTPD_STATUS_TEMPFAIL,
+	OSMTPD_STATUS_PERMFAIL
+};
+
+enum osmtpd_type {
+	OSMTPD_TYPE_FILTER,
+	OSMTPD_TYPE_REPORT
+};
+
+enum osmtpd_phase {
+	OSMTPD_PHASE_CONNECT,
+	OSMTPD_PHASE_HELO,
+	OSMTPD_PHASE_EHLO,
+	OSMTPD_PHASE_STARTTLS,
+	OSMTPD_PHASE_AUTH,
+	OSMTPD_PHASE_MAIL_FROM,
+	OSMTPD_PHASE_RCPT_TO,
+	OSMTPD_PHASE_DATA,
+	OSMTPD_PHASE_DATA_LINE,
+	OSMTPD_PHASE_RSET,
+	OSMTPD_PHASE_QUIT,
+	OSMTPD_PHASE_NOOP,
+	OSMTPD_PHASE_HELP,
+	OSMTPD_PHASE_WIZ,
+	OSMTPD_PHASE_COMMIT,
+	OSMTPD_PHASE_LINK_CONNECT,
+	OSMTPD_PHASE_LINK_DISCONNECT,
+	OSMTPD_PHASE_LINK_IDENTIFY,
+	OSMTPD_PHASE_LINK_TLS,
+	OSMTPD_PHASE_TX_BEGIN,
+	OSMTPD_PHASE_TX_MAIL,
+	OSMTPD_PHASE_TX_RCPT,
+	OSMTPD_PHASE_TX_ENVELOPE,
+	OSMTPD_PHASE_TX_DATA,
+	OSMTPD_PHASE_TX_COMMIT,
+	OSMTPD_PHASE_TX_ROLLBACK,
+	OSMTPD_PHASE_PROTOCOL_CLIENT,
+	OSMTPD_PHASE_PROTOCOL_SERVER,
+	OSMTPD_PHASE_FILTER_RESPONSE,
+	OSMTPD_PHASE_TIMEOUT
+};
+
+#define OSMTPD_NEED_SRC 1 << 0
+#define OSMTPD_NEED_DST 1 << 1
+#define OSMTPD_NEED_RDNS 1 << 2
+#define OSMTPD_NEED_FRDNS 1 << 3
+#define OSMTPD_NEED_IDENTITY 1 << 4
+#define OSMTPD_NEED_CIPHERS 1 << 5
+#define OSMTPD_NEED_MSGID 1 << 6
+#define OSMTPD_NEED_MAILFROM 1 << 7
+#define OSMTPD_NEED_RCPTTO 1 << 8
+#define OSMTPD_NEED_EVPID 1 << 9
+
+struct osmtpd_ctx {
+	enum osmtpd_type	 type;
+	int			 version_major;
+	int			 version_minor;
+	struct timespec		 tm;
+	int			 incoming;
+	enum osmtpd_phase	 phase;
+	uint64_t		 reqid;
+	uint64_t		 token;
+	struct sockaddr_storage	 src;
+	struct sockaddr_storage	 dst;
+	char			*rdns;
+	char			*fcrdns;
+	char			*identity;
+	char			*ciphers;
+	uint32_t		 msgid;
+	char			*mailfrom;
+	char			**rcptto;
+	uint64_t		 evpid;
+	void			*local;
+};
+
+void osmtpd_register_filter_connect(void (*)(struct osmtpd_ctx *, const char *,
+    struct sockaddr_storage *));
+void osmtpd_register_filter_helo(void (*)(struct osmtpd_ctx *, const char *));
+void osmtpd_register_filter_ehlo(void (*)(struct osmtpd_ctx *, const char *));
+void osmtpd_register_filter_starttls(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_auth(void (*)(struct osmtpd_ctx *, const char *));
+void osmtpd_register_filter_mailfrom(void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_filter_rcptto(void (*)(struct osmtpd_ctx *, const char *));
+void osmtpd_register_filter_data(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_dataline(void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_filter_rset(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_quit(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_noop(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_help(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_wiz(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_filter_commit(void (*)(struct osmtpd_ctx *));
+void osmtpd_register_report_connect(int, void (*)(struct osmtpd_ctx *,
+    const char *, const char *, struct sockaddr_storage *,
+    struct sockaddr_storage *));
+void osmtpd_register_report_disconnect(int, void (*)(struct osmtpd_ctx *));
+void osmtpd_register_report_identify(int, void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_report_tls(int, void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_report_begin(int, void (*)(struct osmtpd_ctx *, uint32_t));
+void osmtpd_register_report_mail(int, void (*)(struct osmtpd_ctx *, uint32_t,
+    const char *, enum osmtpd_status));
+void osmtpd_register_report_rcpt(int, void (*)(struct osmtpd_ctx *, uint32_t,
+    const char *, enum osmtpd_status));
+void osmtpd_register_report_envelope(int, void (*)(struct osmtpd_ctx *, uint32_t,
+    uint64_t));
+void osmtpd_register_report_data(int, void (*)(struct osmtpd_ctx *, uint32_t,
+    enum osmtpd_status));
+void osmtpd_register_report_commit(int, void (*)(struct osmtpd_ctx *, uint32_t,
+    size_t));
+void osmtpd_register_report_rollback(int, void (*)(struct osmtpd_ctx *,
+    uint32_t));
+void osmtpd_register_report_client(int, void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_report_server(int, void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_report_response(int, void (*)(struct osmtpd_ctx *,
+    const char *));
+void osmtpd_register_report_timeout(int, void (*)(struct osmtpd_ctx *));
+void osmtpd_localdata(void *(*)(struct osmtpd_ctx *),
+    void (*)(struct osmtpd_ctx *, void *));
+void osmtpd_need(int, int);
+
+void osmtpd_filter_proceed(struct osmtpd_ctx *);
+void osmtpd_filter_reject(struct osmtpd_ctx *, int, const char *, ...)
+	__attribute__((__format__ (printf, 3, 4)));
+void osmtpd_filter_disconnect(struct osmtpd_ctx *, const char *, ...)
+	__attribute__((__format__ (printf, 2, 3)));
+void osmtpd_filter_dataline(struct osmtpd_ctx *, const char *, ...)
+	__attribute__((__format__ (printf, 2, 3)));
+void osmtpd_run(void);
blob - /dev/null
blob + 77e3caf57b2a00d8ffc439f99d39c0db3975040c (mode 644)
--- /dev/null
+++ opensmtpd_priv.h
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+struct osmtpd_callback {
+	enum osmtpd_type type;
+	enum osmtpd_phase phase;
+	int incoming;
+	void (*osmtpd_cb)(struct osmtpd_callback *, struct osmtpd_ctx *, char *,
+	    char *);
+	void *cb;
+	int doregister;
+	int storereport;
+};
+
+struct osmtpd_session {
+	struct osmtpd_ctx ctx;
+	RB_ENTRY(osmtpd_session) entry;
+};
+
+static void osmtpd_register(enum osmtpd_type, enum osmtpd_phase, int, int,
+    void *);
+static const char *osmtpd_typetostr(enum osmtpd_type);
+static const char *osmtpd_phasetostr(enum osmtpd_phase);
+static enum osmtpd_phase osmtpd_strtophase(const char *, const char *);
+static void osmtpd_newline(struct io *, int, void *);
+static void osmtpd_outevt(struct io *, int, void *);
+static void osmtpd_noargs(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_onearg(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_connect(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_link_connect(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_link_disconnect(struct osmtpd_callback *,
+    struct osmtpd_ctx *, char *, char *);
+static void osmtpd_link_identify(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_link_tls(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_begin(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_mail(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_rcpt(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_envelope(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_data(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_commit(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_tx_rollback(struct osmtpd_callback *, struct osmtpd_ctx *,
+    char *, char *);
+static void osmtpd_addrtoss(char *, struct sockaddr_storage *, int, char *);
+static enum osmtpd_status osmtpd_strtostatus(const char *, char *);
+static int osmtpd_session_cmp(struct osmtpd_session *, struct osmtpd_session *);
+static void *(*oncreatecb)(struct osmtpd_ctx *) = NULL;
+static void (*ondeletecb)(struct osmtpd_ctx *, void *) = NULL;
+
+
+static struct osmtpd_callback osmtpd_callbacks[] = {
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_CONNECT,
+	    1,
+	    osmtpd_connect,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_HELO,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_EHLO,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_STARTTLS,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_AUTH,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_MAIL_FROM,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_RCPT_TO,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_DATA,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_DATA_LINE,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_RSET,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_QUIT,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_NOOP,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_HELP,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_WIZ,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_FILTER,
+	    OSMTPD_PHASE_COMMIT,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_CONNECT,
+	    1,
+	    osmtpd_link_connect,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_DISCONNECT,
+	    1,
+	    osmtpd_link_disconnect,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_IDENTIFY,
+	    1,
+	    osmtpd_link_identify,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_TLS,
+	    1,
+	    osmtpd_link_tls,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_BEGIN,
+	    1,
+	    osmtpd_tx_begin,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_MAIL,
+	    1,
+	    osmtpd_tx_mail,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_RCPT,
+	    1,
+	    osmtpd_tx_rcpt,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_ENVELOPE,
+	    1,
+	    osmtpd_tx_envelope,
+	    NULL,
+	    0,
+	   0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_DATA,
+	    1,
+	    osmtpd_tx_data,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_COMMIT,
+	    1,
+	    osmtpd_tx_commit,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_ROLLBACK,
+	    1,
+	    osmtpd_tx_rollback,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_PROTOCOL_CLIENT,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_PROTOCOL_SERVER,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_FILTER_RESPONSE,
+	    1,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TIMEOUT,
+	    1,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_CONNECT,
+	    0,
+	    osmtpd_link_connect,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_DISCONNECT,
+	    0,
+	    osmtpd_link_disconnect,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_IDENTIFY,
+	    0,
+	    osmtpd_link_identify,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_LINK_TLS,
+	    0,
+	    osmtpd_link_tls,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_BEGIN,
+	    0,
+	    osmtpd_tx_begin,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_MAIL,
+	    0,
+	    osmtpd_tx_mail,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_RCPT,
+	    0,
+	    osmtpd_tx_rcpt,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_ENVELOPE,
+	    0,
+	    osmtpd_tx_envelope,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_DATA,
+	    0,
+	    osmtpd_tx_data,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_COMMIT,
+	    0,
+	    osmtpd_tx_commit,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TX_ROLLBACK,
+	    0,
+	    osmtpd_tx_rollback,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_PROTOCOL_CLIENT,
+	    0,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_PROTOCOL_SERVER,
+	    0,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_FILTER_RESPONSE,
+	    0,
+	    osmtpd_onearg,
+	    NULL,
+	    0,
+	    0
+	},
+	{
+	    OSMTPD_TYPE_REPORT,
+	    OSMTPD_PHASE_TIMEOUT,
+	    0,
+	    osmtpd_noargs,
+	    NULL,
+	    0,
+	    0
+	}
+};
blob - /dev/null
blob + 3d7c908e43d641cb0dcddbee5df35d0ac5910b46 (mode 644)
--- /dev/null
+++ shlib_version
@@ -0,0 +1,2 @@
+major=0
+minor=1