Import pop3d.
authorSunil Nimmagadda <sunil@nimmagadda.net>
Thu, 27 Mar 2014 09:53:52 +0500
changeset 0 9e2cb1ed20b1
child 1 f50606f97adf
Import pop3d.
.gitignore
Makefile
imsgev.c
imsgev.h
iobuf.c
iobuf.h
ioev.c
ioev.h
maildir.c
maildrop.c
mbox.c
pop3d.8
pop3d.c
pop3d.h
pop3e.c
session.c
ssl.c
ssl.h
ssl_privsep.c
util.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.gitignore	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,3 @@
+CVS/
+obj/
+tags
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,14 @@
+PROG=		pop3d
+MAN=		pop3d.8
+CFLAGS+=	-Wall -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=	-Wmissing-declarations -Wshadow -Wpointer-arith
+CFLAGS+=	-Wcast-qual -Wsign-compare
+CFLAGS+=	-DIO_SSL
+DEBUG=		-g
+SRCS=		pop3d.c pop3e.c session.c maildrop.c maildir.c mbox.c util.c
+SRCS+=		imsgev.c iobuf.c ioev.c
+SRCS+=		ssl.c ssl_privsep.c
+LDADD+=		-levent -lssl -lcrypto -lutil
+DPADD=		${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
+
+.include <bsd.prog.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imsgev.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2009 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/param.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "imsgev.h"
+
+void imsgev_add(struct imsgev *);
+void imsgev_dispatch(int, short, void *);
+void imsgev_disconnect(struct imsgev *, int);
+
+void
+imsgev_init(struct imsgev *iev, int fd, void *data,
+    void (*callback)(struct imsgev *, int, struct imsg *),
+    void (*needfd)(struct imsgev *))
+{
+	imsg_init(&iev->ibuf, fd);
+	iev->terminate = 0;
+
+	iev->data = data;
+	iev->handler = imsgev_dispatch;
+	iev->callback = callback;
+	iev->needfd = needfd;
+
+	iev->events = EV_READ;
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+	event_add(&iev->ev, NULL);
+}
+
+int
+imsgev_compose(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+    uint32_t pid, int fd, void *data, u_int16_t datalen)
+{
+	int	r;
+
+	r = imsg_compose(&iev->ibuf, type, peerid, pid, fd, data, datalen);
+	if (r != -1)
+		imsgev_add(iev);
+
+	return (r);
+}
+
+void
+imsgev_close(struct imsgev *iev)
+{
+	iev->terminate = 1;
+	imsgev_add(iev);
+}
+
+void
+imsgev_clear(struct imsgev *iev)
+{
+	event_del(&iev->ev);
+	msgbuf_clear(&iev->ibuf.w);
+	close(iev->ibuf.fd);
+}
+
+void
+imsgev_add(struct imsgev *iev)
+{
+	short	events = 0;
+
+	if (!iev->terminate)
+		events = EV_READ;
+	if (iev->ibuf.w.queued || iev->terminate)
+		events |= EV_WRITE;
+
+	/* optimization: skip event_{del/set/add} if already set */
+	if (events == iev->events)
+		return;
+
+	iev->events = events;
+	event_del(&iev->ev);
+	event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+	event_add(&iev->ev, NULL);
+}
+
+void
+imsgev_dispatch(int fd, short ev, void *humppa)
+{
+	struct imsgev	*iev = humppa;
+	struct imsgbuf	*ibuf = &iev->ibuf;
+	struct imsg	 imsg;
+	ssize_t		 n;
+
+	iev->events = 0;
+
+	if (ev & EV_READ) {
+		if ((n = imsg_read(ibuf)) == -1) {
+			/* if we don't have enough fds, free one up and retry */
+			if (errno == EAGAIN) {
+				iev->needfd(iev);
+				n = imsg_read(ibuf);
+			}
+
+			if (n == -1) {
+				imsgev_disconnect(iev, IMSGEV_EREAD);
+				return;
+			}
+		}
+		if (n == 0) {
+			/*
+			 * Connection is closed for reading, and we assume
+			 * it is also closed for writing, so we error out
+			 * if write data is pending.
+			 */
+			imsgev_disconnect(iev,
+			    (iev->ibuf.w.queued) ? IMSGEV_EWRITE : IMSGEV_DONE);
+			return;
+		}
+	}
+
+	if (ev & EV_WRITE) {
+		/*
+		 * We wanted to write data out but the connection is either
+		 * closed, or some error occured. Both case are not recoverable
+		 * from the imsg perspective, so we treat it as a WRITE error.
+		 */
+		if ((n = msgbuf_write(&ibuf->w)) != 1) {
+			imsgev_disconnect(iev, IMSGEV_EWRITE);
+			return;
+		}
+	}
+
+	while (iev->terminate == 0) {
+		if ((n = imsg_get(ibuf, &imsg)) == -1) {
+			imsgev_disconnect(iev, IMSGEV_EIMSG);
+			return;
+		}
+		if (n == 0)
+			break;
+		iev->callback(iev, IMSGEV_IMSG, &imsg);
+		imsg_free(&imsg);
+	}
+
+	if (iev->terminate && iev->ibuf.w.queued == 0) {
+		imsgev_disconnect(iev, IMSGEV_DONE);
+		return;
+	}
+
+	imsgev_add(iev);
+}
+
+void
+imsgev_disconnect(struct imsgev *iev, int code)
+{
+	iev->callback(iev, code, NULL);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imsgev.h	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009 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.
+ */
+
+#ifndef __IMSGEV_H__
+#define __IMSGEV_H__
+
+#include <event.h>
+#include <imsg.h>
+
+#define IMSG_LEN(m)	((m)->hdr.len - IMSG_HEADER_SIZE)
+
+struct imsgev {
+	struct imsgbuf	 ibuf;
+	void		(*handler)(int, short, void *);
+	struct event	 ev;
+	void		*data;
+	short		 events;
+	int		 terminate;
+	void		(*callback)(struct imsgev *, int, struct imsg *);
+	void		(*needfd)(struct imsgev *);
+};
+
+#define IMSGEV_IMSG	0
+#define IMSGEV_DONE	1
+#define IMSGEV_EREAD	2
+#define IMSGEV_EWRITE	3
+#define IMSGEV_EIMSG	4
+
+void imsgev_init(struct imsgev *, int, void *, void (*)(struct imsgev *,
+    int, struct imsg *), void (*)(struct imsgev *));
+int  imsgev_compose(struct imsgev *, u_int16_t, u_int32_t, u_int32_t, int,
+    void *, u_int16_t);
+void imsgev_close(struct imsgev *);
+void imsgev_clear(struct imsgev *);
+
+#endif /* __IMSGEV_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iobuf.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,467 @@
+/*	$OpenBSD: iobuf.c,v 1.1 2014/01/27 15:49:52 sunil 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef IO_SSL
+#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 = malloc(size)) == NULL)
+		return (-1);
+
+	io->size = size;
+	io->max = max;
+
+	return (0);
+}
+
+void
+iobuf_clear(struct iobuf *io)
+{
+	struct ioqbuf	*q;
+
+	if (io->buf)
+		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 = realloc(io->buf, io->size + n);
+	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 (0);
+}
+
+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_SSL
+
+int
+iobuf_flush_ssl(struct iobuf *io, void *ssl)
+{
+	ssize_t	s;
+
+	while (io->queued)
+		if ((s = iobuf_write_ssl(io, ssl) < 0))
+			return (s);
+
+	return (0);
+}
+
+ssize_t
+iobuf_write_ssl(struct iobuf *io, void *ssl)
+{
+	struct ioqbuf	*q;
+	int		 r;
+	ssize_t		 n;
+
+	q = io->outq;
+	n = SSL_write(ssl, q->buf + q->rpos, q->wpos - q->rpos);
+	if (n <= 0) {
+		switch ((r = SSL_get_error(ssl, 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_SSLERROR);
+			if (r == 0)
+				errno = EPIPE;
+			return (IOBUF_ERROR);
+		default:
+			return (IOBUF_SSLERROR);
+		}
+	}
+	iobuf_drain(io, n);
+
+	return (n);
+}
+
+ssize_t
+iobuf_read_ssl(struct iobuf *io, void *ssl)
+{
+	ssize_t	n;
+	int	r;
+
+	n = SSL_read(ssl, io->buf + io->wpos, iobuf_left(io));
+	if (n < 0) {
+		switch ((r = SSL_get_error(ssl, 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_SSLERROR);
+			if (r == 0)
+				errno = EPIPE;
+			return (IOBUF_ERROR);
+		default:
+			return (IOBUF_SSLERROR);
+		}
+	} else if (n == 0)
+		return (IOBUF_CLOSED);
+
+	io->wpos += n;
+
+	return (n);
+}
+
+#endif /* IO_SSL */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iobuf.h	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,71 @@
+/*	$OpenBSD: iobuf.h,v 1.1 2014/01/27 15:49:52 sunil 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 <stdarg.h>
+
+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_SSLERROR		-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_ssl(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_ssl(struct iobuf *, void *);
+ssize_t	iobuf_write(struct iobuf *, int);
+ssize_t	iobuf_write_ssl(struct iobuf *, void *);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ioev.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,910 @@
+/*	$OpenBSD: ioev.c,v 1.1 2014/01/27 15:49:52 sunil 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 <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_SSL
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#endif
+
+enum {
+	IO_STATE_NONE,
+	IO_STATE_CONNECT,
+	IO_STATE_CONNECT_SSL,
+	IO_STATE_ACCEPT_SSL,
+	IO_STATE_UP,
+
+	IO_STATE_MAX,
+};
+
+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_SSL
+void	ssl_error(const char *); /* XXX external */
+
+static const char* io_ssl_error(void);
+void	io_dispatch_accept_ssl(int, short, void *);
+void	io_dispatch_connect_ssl(int, short, void *);
+void	io_dispatch_read_ssl(int, short, void *);
+void	io_dispatch_write_ssl(int, short, void *);
+void	io_reload_ssl(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_SSL
+	if (io->ssl) {
+		snprintf(ssl, sizeof ssl, " ssl=%s:%s:%i",
+		    SSL_get_cipher_version(io->ssl),
+		    SSL_get_cipher_name(io->ssl),
+		    SSL_get_cipher_bits(io->ssl, NULL));
+	}
+#endif
+
+	if (io->iobuf == NULL)
+		snprintf(buf, sizeof buf,
+		    "<io:%p fd=%i to=%i fl=%s%s>",
+		    io, io->sock, io->timeout, io_strflags(io->flags), ssl);
+	else
+		snprintf(buf, sizeof buf,
+		    "<io:%p fd=%i to=%i 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_TLSVERIFIED);
+	CASE(IO_DATAIN);
+	CASE(IO_LOWAT);
+	CASE(IO_DISCONNECTED);
+	CASE(IO_TIMEOUT);
+	CASE(IO_ERROR);
+	default:
+		snprintf(buf, sizeof(buf), "IO_? %i", evt);
+		return buf;
+	}
+}
+
+void
+io_set_blocking(int fd, int blocking)
+{
+	int	flags;
+
+	if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+		err(1, "io_set_blocking:fcntl(F_GETFL)");
+
+	if (blocking)
+		flags &= ~O_NONBLOCK;
+	else
+		flags |= O_NONBLOCK;
+
+	if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+		err(1, "io_set_blocking:fcntl(F_SETFL)");
+}
+
+void
+io_set_linger(int fd, int linger)
+{
+	struct linger    l;
+
+	bzero(&l, sizeof(l));
+	l.l_onoff = linger ? 1 : 0;
+	l.l_linger = linger;
+	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, beacuse 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
+	 * immediatly 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;
+}
+
+void
+io_init(struct io *io, int sock, void *arg,
+	void(*cb)(struct io*, int), struct iobuf *iobuf)
+{
+	_io_init();
+
+	memset(io, 0, sizeof *io);
+
+	io->sock = sock;
+	io->timeout = -1;
+	io->arg = arg;
+	io->iobuf = iobuf;
+	io->cb = cb;
+
+	if (sock != -1)
+		io_reload(io);
+}
+
+void
+io_clear(struct io *io)
+{
+	io_debug("io_clear(%p)\n", io);
+
+	/* the current io is virtually dead */
+	if (io == current)
+		current = NULL;
+
+#ifdef IO_SSL
+	if (io->ssl) {
+		SSL_shutdown(io->ssl);
+		SSL_free(io->ssl);
+		io->ssl = NULL;
+	}
+#endif
+
+	event_del(&io->ev);
+	if (io->sock != -1) {
+		close(io->sock);
+		io->sock = -1;
+	}
+}
+
+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_timeout(struct io *io, int msec)
+{
+	io_debug("io_set_timeout(%p, %i)\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);
+}
+
+#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;
+
+#ifdef IO_SSL
+	if (io->ssl) {
+		io_reload_ssl(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;
+
+	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);
+}
+
+size_t
+io_queued(struct io *io)
+{
+	return iobuf_queued(io->iobuf);
+}
+
+const char*
+io_strflags(int flags)
+{
+	static char	buf[64];
+
+	buf[0] = '\0';
+
+	switch (flags & IO_RW) {
+	case 0:
+		strlcat(buf, "rw", sizeof buf);
+		break;
+	case IO_READ:
+		strlcat(buf, "R", sizeof buf);
+		break;
+	case IO_WRITE:
+		strlcat(buf, "W", sizeof buf);
+		break;
+	case IO_RW:
+		strlcat(buf, "RW", sizeof buf);
+		break;
+	}
+
+	if (flags & IO_PAUSE_IN)
+		strlcat(buf, ",F_PI", sizeof buf);
+	if (flags & IO_PAUSE_OUT)
+		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) {
+		strlcat(buf, "<NONE>", sizeof(buf));
+		return buf;
+	}
+
+	if (ev & EV_TIMEOUT) {
+		strlcat(buf, "EV_TIMEOUT", sizeof(buf));
+		ev &= ~EV_TIMEOUT;
+		n++;
+	}
+
+	if (ev & EV_READ) {
+		if (n)
+			strlcat(buf, "|", sizeof(buf));
+		strlcat(buf, "EV_READ", sizeof(buf));
+		ev &= ~EV_READ;
+		n++;
+	}
+
+	if (ev & EV_WRITE) {
+		if (n)
+			strlcat(buf, "|", sizeof(buf));
+		strlcat(buf, "EV_WRITE", sizeof(buf));
+		ev &= ~EV_WRITE;
+		n++;
+	}
+
+	if (ev & EV_SIGNAL) {
+		if (n)
+			strlcat(buf, "|", sizeof(buf));
+		strlcat(buf, "EV_SIGNAL", sizeof(buf));
+		ev &= ~EV_SIGNAL;
+		n++;
+	}
+
+	if (ev) {
+		if (n)
+			strlcat(buf, "|", sizeof(buf));
+		strlcat(buf, "EV_?=0x", sizeof(buf));
+		snprintf(buf2, sizeof(buf2), "%hx", ev);
+		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) {
+		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);
+}
+
+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_blocking(sock, 0);
+	io_set_linger(sock, 0);
+
+	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_SSL
+
+static const char*
+io_ssl_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 SSL error");
+}
+
+int
+io_start_tls(struct io *io, void *ssl)
+{
+	int	mode;
+
+	mode = io->flags & IO_RW;
+	if (mode == 0 || mode == IO_RW)
+		errx(1, "io_start_tls(): full-duplex or unset");
+
+	if (io->ssl)
+		errx(1, "io_start_tls(): SSL already started");
+	io->ssl = ssl;
+
+	if (SSL_set_fd(io->ssl, io->sock) == 0) {
+		ssl_error("io_start_ssl:SSL_set_fd");
+		return (-1);
+	}
+
+	if (mode == IO_WRITE) {
+		io->state = IO_STATE_CONNECT_SSL;
+		SSL_set_connect_state(io->ssl);
+		io_reset(io, EV_WRITE, io_dispatch_connect_ssl);
+	} else {
+		io->state = IO_STATE_ACCEPT_SSL;
+		SSL_set_accept_state(io->ssl);
+		io_reset(io, EV_READ, io_dispatch_accept_ssl);
+	}
+
+	return (0);
+}
+
+void
+io_dispatch_accept_ssl(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 e, ret;
+
+	io_frame_enter("io_dispatch_accept_ssl", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	if ((ret = SSL_accept(io->ssl)) > 0) {
+		io->state = IO_STATE_UP;
+		io_callback(io, IO_TLSREADY);
+		goto leave;
+	}
+
+	switch ((e = SSL_get_error(io->ssl, ret))) {
+	case SSL_ERROR_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_accept_ssl);
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_accept_ssl);
+		break;
+	default:
+		io->error = io_ssl_error();
+		ssl_error("io_dispatch_accept_ssl:SSL_accept");
+		io_callback(io, IO_ERROR);
+		break;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_connect_ssl(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 e, ret;
+
+	io_frame_enter("io_dispatch_connect_ssl", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	if ((ret = SSL_connect(io->ssl)) > 0) {
+		io->state = IO_STATE_UP;
+		io_callback(io, IO_TLSREADY);
+		goto leave;
+	}
+
+	switch ((e = SSL_get_error(io->ssl, ret))) {
+	case SSL_ERROR_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_connect_ssl);
+		break;
+	case SSL_ERROR_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_connect_ssl);
+		break;
+	default:
+		io->error = io_ssl_error();
+		ssl_error("io_dispatch_connect_ssl:SSL_connect");
+		io_callback(io, IO_ERROR);
+		break;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_read_ssl(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 n, saved_errno;
+
+	io_frame_enter("io_dispatch_read_ssl", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+again:
+	switch ((n = iobuf_read_ssl(io->iobuf, (SSL*)io->ssl))) {
+	case IOBUF_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_read_ssl);
+		break;
+	case IOBUF_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_read_ssl);
+		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_SSLERROR:
+		io->error = io_ssl_error();
+		ssl_error("io_dispatch_read_ssl:SSL_read");
+		io_callback(io, IO_ERROR);
+		break;
+	default:
+		io_debug("io_dispatch_read_ssl(...) -> r=%i\n", n);
+		io_callback(io, IO_DATAIN);
+		if (current == io && IO_READING(io) && SSL_pending(io->ssl))
+			goto again;
+	}
+
+    leave:
+	io_frame_leave(io);
+}
+
+void
+io_dispatch_write_ssl(int fd, short event, void *humppa)
+{
+	struct io	*io = humppa;
+	int		 n, saved_errno;
+	size_t		 w2, w;
+
+	io_frame_enter("io_dispatch_write_ssl", io, event);
+
+	if (event == EV_TIMEOUT) {
+		io_callback(io, IO_TIMEOUT);
+		goto leave;
+	}
+
+	w = io_queued(io);
+	switch ((n = iobuf_write_ssl(io->iobuf, (SSL*)io->ssl))) {
+	case IOBUF_WANT_READ:
+		io_reset(io, EV_READ, io_dispatch_write_ssl);
+		break;
+	case IOBUF_WANT_WRITE:
+		io_reset(io, EV_WRITE, io_dispatch_write_ssl);
+		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_SSLERROR:
+		io->error = io_ssl_error();
+		ssl_error("io_dispatch_write_ssl:SSL_write");
+		io_callback(io, IO_ERROR);
+		break;
+	default:
+		io_debug("io_dispatch_write_ssl(...) -> w=%i\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_ssl(struct io *io)
+{
+	short	ev = 0;
+	void	(*dispatch)(int, short, void*) = NULL;
+
+	switch (io->state) {
+	case IO_STATE_CONNECT_SSL:
+		ev = EV_WRITE;
+		dispatch = io_dispatch_connect_ssl;
+		break;
+	case IO_STATE_ACCEPT_SSL:
+		ev = EV_READ;
+		dispatch = io_dispatch_accept_ssl;
+		break;
+	case IO_STATE_UP:
+		ev = 0;
+		if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) {
+			ev = EV_READ;
+			dispatch = io_dispatch_read_ssl;
+		}
+		else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io)) {
+			ev = EV_WRITE;
+			dispatch = io_dispatch_write_ssl;
+		}
+		if (! ev)
+			return; /* paused */
+		break;
+	default:
+		errx(1, "io_reload_ssl(): bad state");
+	}
+
+	io_reset(io, ev, dispatch);
+}
+
+#endif /* IO_SSL */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ioev.h	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,69 @@
+/*	$OpenBSD: ioev.h,v 1.1 2014/01/27 15:49:52 sunil 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 <event.h>
+
+enum {
+	IO_CONNECTED = 0, 	/* connection successful	*/
+	IO_TLSREADY,		/* TLS started succesfully	*/
+	IO_TLSVERIFIED,		/* 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_READ			0x01
+#define IO_WRITE		0x02
+#define IO_RW			(IO_READ | IO_WRITE)
+#define IO_PAUSE_IN		0x04
+#define IO_PAUSE_OUT		0x08
+#define IO_RESET		0x10  /* internal */
+#define IO_HELD			0x20  /* internal */
+
+struct iobuf;
+struct io {
+	int		 sock;
+	void		*arg;
+	void		(*cb)(struct io*, int);
+	struct iobuf	*iobuf;
+	size_t		 lowat;
+	int		 timeout;
+	int		 flags;
+	int		 state;
+	struct event	 ev;
+	void		*ssl;
+	const char	*error; /* only valid immediatly on callback */
+};
+
+void io_set_blocking(int, int);
+void io_set_linger(int, int);
+
+void io_init(struct io*, int, void*, void(*)(struct io*, int), struct iobuf*);
+void io_clear(struct io*);
+void io_set_read(struct io *);
+void io_set_write(struct io *);
+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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/maildir.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/tree.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <fcntl.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "pop3d.h"
+
+static int init(struct mdrop *, size_t *, size_t *);
+static int retr(struct mdrop *, unsigned int, size_t *, size_t *);
+static int update(struct mdrop *);
+static int new_to_cur(struct mdrop *);
+static int msgcmp(struct msg *, struct msg *);
+RB_PROTOTYPE(msgtree, msg, e.t_entry, msgcmp);
+
+struct m_backend m_backend_maildir = {
+	init,
+	retr,
+	update
+};
+
+/*
+ * No resource management on error path as the process is
+ * killed if an error occurs.
+ */
+static int
+init(struct mdrop *m, size_t *nmsgs, size_t *sz)
+{
+	SHA1_CTX	ctx;
+	struct stat	sb;
+	char		buf[MAXBSIZE];
+	DIR		*dirp;
+	struct dirent	*dp;
+	struct msg	*msg;
+	u_char		*C;
+	size_t		i;
+	ssize_t		len;
+	int		cur_fd, msg_fd;
+
+	*nmsgs = 0;
+	*sz = 0;
+	if (new_to_cur(m) == -1) {
+		logit(LOG_WARNING, "maildir: move msgs from new to cur failed");
+		return (-1);
+	}
+
+	if ((cur_fd = openat(m->fd, "cur", O_RDONLY)) == -1) {
+		logit(LOG_CRIT, "maildir: unable to open \"cur\" dir");
+		return (-1);
+	}
+
+	if ((dirp = fdopendir(cur_fd)) == NULL)
+		return (-1);
+
+	while ((dp = readdir(dirp))) {
+		if (dp->d_type != DT_REG)
+			continue;
+
+		if (strcmp(dp->d_name, ".") == 0 ||
+		    strcmp(dp->d_name, "..") == 0)
+			continue;
+
+		msg = xcalloc(1, sizeof(*msg), "init");
+		if ((msg->u.fname = strdup(dp->d_name)) == NULL)
+			fatalx("init: strdup");
+
+		if (fstatat(cur_fd, dp->d_name, &sb, 0) == -1) {
+			logit(LOG_CRIT, "%s fstatat failed", dp->d_name);
+			return (-1);
+		}
+
+		msg->sz = sb.st_size;
+		if ((msg_fd = openat(cur_fd, dp->d_name, O_RDONLY)) == -1) {
+			logit(LOG_CRIT, "%s openat failed", dp->d_name);
+			return (-1);
+		}
+
+		SHA1Init(&ctx);
+		while (( len = read(msg_fd, buf, sizeof(buf))) > 0) {
+			SHA1Update(&ctx, (u_int8_t *)buf, len);
+			for (C = buf;len--; ++C)
+				if (*C == '\n')
+					msg->nlines += 1;
+		}
+
+		SHA1End(&ctx, msg->hash);
+		close(msg_fd);
+		RB_INSERT(msgtree, &m->e.t_msgs, msg);
+		m->nmsgs += 1;
+	}
+
+	/* allocate space for nmsgs of struct msg pointers */
+	m->msgs_index = xcalloc(m->nmsgs, sizeof(msg), "init");
+	*nmsgs = m->nmsgs;
+	i = 0;
+	*sz = 0;
+	RB_FOREACH(msg, msgtree, &m->e.t_msgs) {
+		m->msgs_index[i++] = msg;
+		/* calculate maildir size by counting newline as 2 (CRLF) */
+		*sz += msg->sz + msg->nlines;
+	}
+
+	closedir(dirp);
+	close(cur_fd);
+	return (0);
+}
+
+static int
+new_to_cur(struct mdrop *m)
+{
+	DIR		*dirp;
+	struct dirent	*dp;
+	int		cur_fd, new_fd;
+
+
+	if ((cur_fd = openat(m->fd, "cur", O_RDONLY)) == -1) {
+		logit(LOG_CRIT, "maildir: unable to open \"cur\" dir");
+		return (-1);
+	}
+
+	if ((new_fd = openat(m->fd, "new", O_RDONLY)) == -1) {
+		logit(LOG_CRIT, "maildir: unable to open \"new\" dir");
+		return (-1);
+	}
+
+	if ((dirp = fdopendir(new_fd)) == NULL)
+		return (-1);
+
+	while ((dp = readdir(dirp))) {
+		if (dp->d_type != DT_REG)
+			continue;
+
+		if (strcmp(dp->d_name, ".") == 0 ||
+		    strcmp(dp->d_name, "..") == 0)
+			continue;
+
+		if(renameat(new_fd, dp->d_name, cur_fd, dp->d_name) == -1) {
+			logit(LOG_CRIT, "maildir: renameat failed");
+			return (-1);
+		}
+	}
+
+	closedir(dirp);
+	close(cur_fd);
+	close(new_fd);
+	return (0);
+}
+
+static int
+retr(struct mdrop *m, unsigned int idx, size_t *nlines, size_t *offset)
+{
+	char	buf[MAXPATHLEN];
+	int	fd, r;
+
+	*offset = 0;
+	r = snprintf(buf, sizeof(buf), "cur/%s", m->msgs_index[idx]->u.fname);
+	if ((u_int)r >= sizeof(buf)) {
+		logit(LOG_WARNING, "path too long");
+		return (-1);
+	}
+
+	fd = openat(m->fd, buf, O_RDONLY);
+	return (fd);
+}
+
+static int
+update(struct mdrop *m)
+{
+	char	buf[MAXPATHLEN];
+	size_t	i, j = 0;
+	int	r;
+
+	for (i = 0; i < m->nmsgs; i++)
+		if (m->msgs_index[i]->flags & F_DELE)
+			j += 1;
+
+	if (j == 0) /* nothing to update */
+		return (0);
+
+	for (i = 0; i < m->nmsgs; i++) {
+		if (!(m->msgs_index[i]->flags & F_DELE))
+			continue;
+
+		r = snprintf(buf, sizeof(buf), "cur/%s",
+		    m->msgs_index[i]->u.fname);
+		if ((u_int)r >= sizeof(buf)) {
+			logit(LOG_WARNING, "path too long");
+			return (1);
+		}
+
+		if (unlinkat(m->fd, buf, 0) == -1) {
+			logit(LOG_CRIT, "%s unlink failed", buf);
+			return (1);
+		}
+	}
+
+	return (0);
+}
+
+static int
+msgcmp(struct msg *m1, struct msg *m2)
+{
+	return strcmp(m1->u.fname, m2->u.fname);
+}
+
+RB_GENERATE(msgtree, msg, e.t_entry, msgcmp);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/maildrop.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "imsgev.h"
+#include "pop3d.h"
+
+static void session_imsgev(struct imsgev *, int, struct imsg *);
+static void update(struct imsgev *, struct imsg *, struct m_backend *);
+static void retr(struct imsgev *, struct imsg *, struct m_backend *);
+static void dele(struct imsgev *, struct imsg *, struct m_backend *);
+static void rset(struct imsgev *, struct imsg *, struct m_backend *);
+static void list(struct imsgev *, struct imsg *, struct m_backend *);
+static void list_all(struct imsgev *, struct imsg *, struct m_backend *, int);
+static void do_list(unsigned int, size_t *, char *, size_t);
+static void *do_list_all(int, size_t *);
+static struct m_backend *m_backend_lookup(enum m_type);
+static void sig_handler(int, short, void *);
+static void needfd(struct imsgev *);
+static size_t expand(char *, const char *, size_t, struct passwd *);
+
+static struct mdrop m;
+
+void
+maildrop_init(uint32_t session_id, int pair[2], struct passwd *pw,
+    int type, const char *path)
+{
+	struct imsgev		iev_session;
+	struct event		ev_sigint, ev_sigterm;
+	struct stats		stats;
+	struct m_backend	*mb;
+	char			buf[MAXPATHLEN];
+	pid_t			pid;
+	mode_t			old_mask;
+	int			fd, flags, res = -1;
+
+	if (seteuid(pw->pw_uid) < 0)
+		fatal("cannot lower privileges");
+
+	pid = fork();
+	if (seteuid(0) < 0)
+		fatal("cannot restore privileges");
+
+	if (pid < 0)
+		fatal("maildrop: fork");
+
+	if (pid > 0)
+		return;
+
+	if (seteuid(pw->pw_uid) < 0)
+		fatal("cannot lower privileges");
+
+	close(pair[0]);
+	setproctitle("maildrop");
+	if ((mb = m_backend_lookup(type)) == NULL)
+		fatalx("maildrop: invalid backend");
+
+	if (expand(buf, path, sizeof(buf), pw) >= sizeof(buf))
+		fatalx("maildrop: path truncation");
+
+	flags = O_CREAT;
+	if (type == M_MBOX)
+		flags |= O_RDWR;
+	else
+		flags |= O_RDONLY;
+
+	old_mask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+	if ((fd = open(buf, flags)) == -1)
+		logit(LOG_CRIT, "%zu: failed to open %s", session_id , buf);
+
+	if (fd != -1) {
+		m.fd = fd;
+		res = mb->init(&m, &stats.nmsgs, &stats.sz);
+	}
+
+	umask(old_mask);
+	if (seteuid(0) < 0)
+		fatal("cannot restore privileges");
+
+	if (setgroups(1, &pw->pw_gid) ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+		fatal("cannot drop privileges");
+
+	event_init();
+	signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL);
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+	imsgev_init(&iev_session, pair[1], mb, session_imsgev, needfd);
+
+	if (res == 0) {
+		imsgev_xcompose(&iev_session, IMSG_MAILDROP_INIT, session_id,
+		    0, -1, &stats, sizeof(struct stats), "maildrop_init");
+	} else {
+		logit(LOG_CRIT, "%zu: maildrop init failed %s",
+		    session_id, buf);
+		imsgev_xcompose(&iev_session, IMSG_MAILDROP_INIT, session_id,
+		    0, -1, NULL, 0, "maildrop_init");
+	}
+
+	if (event_dispatch() < 0)
+		fatal("event_dispatch");
+
+	logit(LOG_INFO, "maildrop process exiting");
+	_exit(0);
+}
+
+/*
+ * Build dst by substituting '~' with user's home dir and '%u' with user name
+ * in src. Return the length of string built. If return value >= dst_sz then
+ * dst is truncated. 
+ */
+static size_t
+expand(char *dst, const char *src, size_t dst_sz, struct passwd *pw)
+{
+	size_t	i = 0, r;
+	int	c;
+
+	while ((c = *src++)) {
+		if (i >= dst_sz)
+			break;
+
+		switch (c) {
+		case '~':
+			if ((r = strlcpy(&dst[i], pw->pw_dir,
+			    (dst_sz - i))) >= (dst_sz - i)) {
+				i += r;
+				goto end;
+			}
+			i += r;
+			break;
+		case '%':
+			if (*src == 'u') {
+				if ((r = strlcpy(&dst[i], pw->pw_name,
+				    (dst_sz - i))) >= (dst_sz - i)) {
+					i += r;
+					goto end;
+				}
+				i += r;
+				src++;
+			} else
+				dst[i++] = c;
+			break;
+		default:
+			dst[i++] = c;
+			break;
+		}
+	}
+
+end:
+	if (c)
+		while ((c = *src++))
+			i++;
+
+	dst[dst_sz - 1] = '\0';
+	return (i);
+}
+
+static void
+session_imsgev(struct imsgev *iev, int code, struct imsg *imsg)
+{
+	struct m_backend	*mb = iev->data;
+	int			uidl = 0;		
+
+	switch (code) {
+	case IMSGEV_IMSG:
+		switch (imsg->hdr.type) {
+		case IMSG_MAILDROP_UPDATE:
+			update(iev, imsg, mb);
+			break;
+		case IMSG_MAILDROP_RETR:
+			retr(iev, imsg, mb);
+			break;
+		case IMSG_MAILDROP_DELE:
+			dele(iev, imsg, mb);
+			break;
+		case IMSG_MAILDROP_RSET:
+			rset(iev, imsg, mb);
+			break;
+		case IMSG_MAILDROP_LIST:
+			list(iev, imsg, mb);
+			break;
+		case IMSG_MAILDROP_UIDLALL:
+			uidl = 1;
+			/* FALLTHROUGH */
+		case IMSG_MAILDROP_LISTALL:
+			list_all(iev, imsg, mb, uidl);
+			break;
+		default:
+			logit(LOG_DEBUG, "%s: unexpected imsg %u",
+			    __func__, imsg->hdr.type);
+			break;
+		}
+		break;
+	case IMSGEV_EREAD:
+	case IMSGEV_EWRITE:
+	case IMSGEV_EIMSG:
+		fatal("maildrop: imsgev read/write error");
+		break;
+	case IMSGEV_DONE:
+		event_loopexit(NULL);
+		break;
+	}
+}
+
+static void
+update(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb)
+{
+	int		res;
+	uint32_t	session_id = imsg->hdr.peerid;
+
+	if ((res = mb->update(&m)) == 0)
+		logit(LOG_INFO, "%zu: maildrop updated", session_id);
+	else
+		logit(LOG_CRIT, "%zu: maildrop updated failed", session_id);
+
+	imsgev_xcompose(iev, IMSG_MAILDROP_UPDATE, session_id,  0,
+	    -1, &res, sizeof(res), "maildrop_update");
+}
+
+static void
+retr(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb)
+{
+	struct retr_res	res;
+	struct retr_req	*req = imsg->data;
+	int		fd;
+
+	fd = mb->retr(&m, req->idx, &res.nlines, &res.offset);
+	/* pass on top arguments */
+	res.top = req->top;
+	res.ntop = req->ntop;
+	imsgev_xcompose(iev, IMSG_MAILDROP_RETR, imsg->hdr.peerid, 0,
+	    fd, &res, sizeof(res), "maildrop_retr");
+}
+
+static void
+dele(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb)
+{
+	unsigned int	*idx = imsg->data;
+	int		res = 0;
+
+	if (m.msgs_index[*idx]->flags & F_DELE) {
+		res = -1;
+		goto end;
+	}
+
+	m.msgs_index[*idx]->flags |= F_DELE;
+end:
+	imsgev_xcompose(iev, IMSG_MAILDROP_DELE, imsg->hdr.peerid, 0,
+	    -1, &res, sizeof(res), "maildrop_dele");
+}
+
+static void
+rset(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb)
+{
+	size_t	i;
+
+	for (i = 0; i < m.nmsgs; i++)
+		m.msgs_index[i]->flags = 0;
+
+	imsgev_xcompose(iev, IMSG_MAILDROP_RSET, imsg->hdr.peerid, 0,
+	    -1, NULL, 0, "maildrop_rset");
+}
+
+static void
+list(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb)
+{
+	struct list_req	*req = imsg->data;
+	struct list_res	res;
+	char		hash[SHA1_DIGEST_STRING_LENGTH];
+	size_t		sz;
+
+	res.idx = req->idx;
+	do_list(req->idx, &sz, hash, sizeof(hash));
+	res.uidl = req->uidl;
+	if (res.uidl)
+		strlcpy(res.u.hash, hash, sizeof(res.u.hash));
+	else
+		res.u.sz = sz;
+
+	imsgev_xcompose(iev, IMSG_MAILDROP_LIST, imsg->hdr.peerid, 0,
+	    -1, &res, sizeof(res), "maildrop_list");
+
+}
+
+static void
+list_all(struct imsgev *iev, struct imsg *imsg, struct m_backend *mb, int uidl)
+{
+	void	*res;
+	size_t	sz;
+
+	res = do_list_all(uidl, &sz);
+	/* XXX watchout for sz > MAX_IMSGSIZE */
+	imsgev_xcompose(iev,
+	    (uidl) ? IMSG_MAILDROP_UIDLALL : IMSG_MAILDROP_LISTALL,
+	    imsg->hdr.peerid, 0, -1, res, sz, "maildrop_list");
+}
+
+static void
+do_list(unsigned int idx, size_t *sz, char *hash, size_t hash_sz)
+{
+	if (m.msgs_index[idx]->flags & F_DELE) {
+		*sz = 0;
+		strlcpy(hash, "", hash_sz);
+		return;
+	}
+
+	*sz = m.msgs_index[idx]->sz;
+	strlcpy(hash, m.msgs_index[idx]->hash, hash_sz);
+}
+
+static void *
+do_list_all(int uidl, size_t *sz)
+{
+	size_t	i, j, *nsz = NULL;
+	char	*nhash = NULL;
+
+	if (uidl) {
+		nhash = xcalloc(m.nmsgs, SHA1_DIGEST_STRING_LENGTH, "list_all");
+	} else
+		nsz = xcalloc(m.nmsgs, sizeof(size_t), "list_all");
+
+	for (i = 0; i < m.nmsgs; i++) {
+			
+		if (uidl) {
+			j = i * SHA1_DIGEST_STRING_LENGTH;
+			if (m.msgs_index[i]->flags & F_DELE)
+				nhash[j] = '\0';
+			else
+				strlcpy(nhash + j, m.msgs_index[i]->hash,
+			    	    SHA1_DIGEST_STRING_LENGTH);
+		} else {
+			if (m.msgs_index[i]->flags & F_DELE)
+				nsz[i] = 0;
+			else
+				nsz[i] = m.msgs_index[i]->sz;
+		}
+	}
+
+	if (uidl) {
+		*sz = m.nmsgs * SHA1_DIGEST_STRING_LENGTH;
+		return (nhash);
+	} else {
+		*sz = m.nmsgs * sizeof(size_t);
+		return (nsz);
+	}
+}
+
+static void
+needfd(struct imsgev *iev)
+{
+	fatalx("maildrop should never need an fd");
+}
+
+static void
+sig_handler(int sig, short event, void *arg)
+{
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		event_loopexit(NULL);
+	}
+}
+
+extern struct m_backend m_backend_mbox;
+extern struct m_backend m_backend_maildir;
+
+static struct m_backend *
+m_backend_lookup(enum m_type type)
+{
+	switch (type) {
+	case M_MBOX:
+		return &m_backend_mbox;
+		break;
+	case M_MAILDIR:
+		return &m_backend_maildir;
+		break;
+	default:
+		fatalx("m_backend_lookup: invalid m_type");
+	};
+
+	return (NULL);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbox.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "pop3d.h"
+
+static int init(struct mdrop *, size_t *, size_t *);
+static int retr(struct mdrop *, unsigned int, size_t *, size_t *);
+static int update(struct mdrop *);
+
+struct m_backend m_backend_mbox = {
+	init,
+	retr,
+	update
+};
+
+/* 
+ * Parse mbox calculating each message's offset, size and hash.
+ * A message is identified by "From " at the start of a line.
+ * Returns 0 on success with nmsgs and sz populated.
+ */
+int
+init(struct mdrop *m, size_t *nmsgs, size_t *sz)
+{
+	SHA1_CTX	ctx;
+	FILE		*fp;
+	struct msg	*msg = NULL;
+	size_t		i, len;
+	long		offset;
+	char		*line;
+
+	*nmsgs = 0;
+	*sz = 0;
+	if (flock(m->fd, LOCK_EX|LOCK_NB) == -1) {
+		switch (errno) {
+		case EWOULDBLOCK:
+			logit(LOG_INFO, "mbox: locked by other process");
+			return (-1);
+		default:
+			fatal("flock(LOCK_EX)");
+		}
+	}
+
+	if ((fp = fdopen(dup(m->fd), "r+")) == NULL) {
+		logit(LOG_INFO, "mbox: fdopen failed");
+		return (-1);
+	}
+
+	SIMPLEQ_INIT(&m->e.q_msgs);
+	offset = ftell(fp);
+	while ((line = fgetln(fp, &len))) {
+		if ((len > 5) && strncmp("From ", line, 5) == 0) {
+			if (msg)
+				SHA1End(&ctx, msg->hash);
+
+			msg = xcalloc(1, sizeof(*msg), "init");
+			SHA1Init(&ctx);
+			msg->u.offset = offset;
+			m->nmsgs += 1;
+			SIMPLEQ_INSERT_TAIL(&m->e.q_msgs, msg, e.q_entry);
+		} else {
+			if (msg == NULL)
+				fatalx("mbox corrupted: no \"From \" line");
+
+			msg->sz += len;
+			msg->nlines += 1;
+			SHA1Update(&ctx, (u_int8_t *)line, len);
+			offset = ftell(fp);
+		}
+	}
+
+	if (msg)
+		SHA1End(&ctx, msg->hash);
+
+	/* allocate space for nmsgs of struct msg pointers */
+	m->msgs_index = xcalloc(m->nmsgs, sizeof(msg), "make_index");
+	i = 0;
+	SIMPLEQ_FOREACH(msg, &m->e.q_msgs, e.q_entry) {
+		m->msgs_index[i++] = msg;
+		/* calculate mbox size by counting newline as 2 (CRLF) */
+		m->sz += msg->sz + msg->nlines;
+	}
+
+	*nmsgs = m->nmsgs;
+	*sz = m->sz;
+	fclose(fp);
+	return (0);
+}
+
+static int
+retr(struct mdrop *m, unsigned int idx, size_t *nlines, size_t *offset)
+{
+	if (m->msgs_index[idx]->flags & F_DELE)
+		return (-1);
+
+	*offset = m->msgs_index[idx]->u.offset;
+	*nlines = m->msgs_index[idx]->nlines;
+	return (dup(m->fd)); /* imsg closes sender's fd */
+}
+
+/* 
+ * No resource management as this process is blown away
+ * upon success or error.
+ */
+static int
+update(struct mdrop *m)
+{
+	struct msg	*cur;
+	size_t		i, j = 0, len, nlines;
+	char		buf[MAXBSIZE], fn[22], *line;
+	FILE		*tmp_fp, *m_fp;
+	mode_t		old_mask;
+	int		tmp_fd;
+
+	for (i = 0; i < m->nmsgs; i++)
+		if (m->msgs_index[i]->flags & F_DELE)
+			j += 1;
+
+	if ((m_fp = fdopen(dup(m->fd), "r+")) == NULL) {
+		logit(LOG_INFO, "mbox: fdopen failed");
+		return (-1);
+	}
+
+	if (j == 0)
+		return (0);
+	else if (j == m->nmsgs) {
+		if (ftruncate(fileno(m_fp), 0) == -1) {
+			logit(LOG_CRIT, "update: ftruncate failed");
+			return (1);
+		}
+
+		return (0);
+	}
+
+	strlcpy(fn, "/tmp/pop3d.XXXXXXXXXX", sizeof(fn));
+	old_mask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+	if ((tmp_fd = mkstemp(fn)) == -1 ||
+	    (tmp_fp = fdopen(tmp_fd, "r+")) == NULL) {
+		logit(LOG_CRIT, "mbox: mkstemp failed");
+		return (1);
+	}
+
+	umask(old_mask);
+	for (i = 0; i < m->nmsgs; i++) {
+		cur = m->msgs_index[i];
+		if (cur->flags & F_DELE)
+			continue;
+
+		if (fseek(m_fp, cur->u.offset, SEEK_SET) == -1) {
+			logit(LOG_CRIT, "update: fseek failed");
+			return (1);
+		}
+		/*
+		 * "From " line isn't counted in nlines but offset starts
+		 * there, adjust nlines here 
+		 */
+		nlines = m->msgs_index[i]->nlines + 1;
+		while (nlines--) {
+			if ((line = fgetln(m_fp, &len)))
+				if (fwrite(line, len, 1, tmp_fp) != 1)
+					fatalx("update: short write");
+		}
+	}
+
+	fflush(tmp_fp);
+	rewind(m_fp);
+	rewind(tmp_fp);
+	while (!feof(tmp_fp)) {
+		fread(buf, sizeof(buf), 1, tmp_fp);
+		if (fwrite(buf, sizeof(buf), 1, m_fp) != 1)
+			fatalx("update: short write");
+	}
+
+	fflush(m_fp);
+	if (ftruncate(fileno(m_fp), ftello(tmp_fp)) == -1)
+		fatal("update: failed to truncate");
+
+	fclose(m_fp);
+	return (0);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pop3d.8	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,86 @@
+.\" Copyright (c) Sunil Nimmagadda <sunil@nimmagadda.net>
+.\"
+.\" 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.
+.\"
+.Dd $Mdocdate: March 26 2014 $
+.Dt POP3D 8
+.Os
+.Sh NAME
+.Nm pop3d
+.Nd Post Office Protocol (POP3) daemon.
+.Sh SYNOPSIS
+.Nm
+.Op Fl d
+.Op Fl p Ar path
+.Op Fl t Ar type
+.Sh DESCRIPTION
+.Nm
+daemon implements Post Office Protocol (Version 3) as specified in
+RFC 1939 as well as POP3S and STARTTLS extensions.
+.Pp
+.Nm
+binds to 110(POP3), 995(POP3S) ports and operates on local mailboxes on
+behalf of its remote users.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Do not daemonize. If this option is specified,
+.Nm
+will run in foreground and log to
+.Em stderr .
+.It Fl p
+Path to the maildrop. Defaults to /var/mail/%u in case of mbox and 
+~/Maildir in case of maildir.
+.Nm
+expands '~' to user's home dir
+and '%u' to user's name if specified in the path.
+.It Fl t
+Specify maildrop type. Options are mbox and maildir. Defaults to mbox.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/ssl/private/server.key" -compact
+.It Pa ~/maildir
+.It Pa /var/mail/%u
+User maildrops
+.Pp
+.It /etc/ssl/server.crt
+.It /etc/ssl/private/server.key
+Location of SSL certificate and key
+.Sh SEE ALSO
+.Xr smtpd 8 ,
+.Xr ssl 8
+.Sh STANDARDS
+.Rs
+.%A J. Myers
+.%A M. Rose
+.%D May 1996
+.%R RFC 1939
+.%T Post Office Protocol \(en Version 3
+.Re
+.Pp
+.Rs
+.%A C. Newman
+.%D June 1999
+.%R RFC 2595
+.%T Using TLS with IMAP, POP3 and ACAP
+.Re
+.Pp
+.Rs
+.%A A. Melnikov
+.%A C. Newman
+.%A M. Yevstifeyev
+.%D August 2011
+.%R draft-melnikov-pop3-over-tls-02
+.Sh CAVEATS
+POP3 authenticates using cleartext passwords on 110(POP3) port.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pop3d.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/param.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+#include <bsd_auth.h>
+#include <err.h>
+#include <event.h>
+#include <login_cap.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "imsgev.h"
+#include "pop3d.h"
+
+#define	MBOX_PATH	"/var/mail/%u"
+#define MAILDIR_PATH	"~/Maildir"
+#define	POP3D_USER	"_pop3d"
+
+static void authenticate(struct imsgev *, struct imsg *);
+static void pop3e_imsgev(struct imsgev *, int , struct imsg *);
+static void needfd(struct imsgev *);
+static void sig_handler(int, short, void *);
+static enum m_type m_type(const char *);
+static void usage(void);
+
+static struct imsgev	iev_pop3e;
+static pid_t		pop3e_pid;
+static const char	*mpath = MBOX_PATH;
+static int		mtype = M_MBOX;
+
+int
+main(int argc, char *argv[])
+{
+	struct passwd	*pw;
+	struct event	ev_sigint, ev_sigterm, ev_sighup, ev_sigchld;
+	const char	*mtype_str = "mbox";
+	int		ch, d = 0, pair[2];
+
+	while ((ch = getopt(argc, argv, "dp:t:")) != -1) {
+		switch (ch) {
+		case 'd':
+			d = 1;
+			break;
+		case 'p':
+			mpath = optarg;
+			break;
+		case 't':
+			if ((mtype = m_type(optarg)) == -1)
+				errx(1, "%s invalid argument", optarg);
+			if (mtype == M_MAILDIR)
+				mpath = MAILDIR_PATH;
+			mtype_str = optarg;
+			break;
+		default:
+			usage();
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+	if (argc > 0 || *argv)
+		usage();
+
+	log_init(d);
+	if (geteuid())
+		fatalx("need root privileges");
+
+	if (!d && daemon(1, 0) == -1)
+		fatal("failed to daemonize");
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) == -1)
+		fatal("socketpair");
+
+	set_nonblocking(pair[0]);
+	set_nonblocking(pair[1]);
+	if ((pw = getpwnam(POP3D_USER)) == NULL)
+		fatalx("main: getpwnam " POP3D_USER);
+
+	pop3e_pid = pop3_main(pair, pw);
+	close(pair[1]);
+	setproctitle("[priv]");
+	logit(LOG_INFO, "pop3d ready; type:%s, path:%s", mtype_str, mpath);
+	event_init();
+	signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
+	signal_set(&ev_sighup, SIGHUP, sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL);
+	signal_set(&ev_sigchld, SIGCHLD, sig_handler, NULL);
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sighup, NULL);
+	signal_add(&ev_sigterm, NULL);
+	signal_add(&ev_sigchld, NULL);
+	imsgev_init(&iev_pop3e, pair[0], NULL, pop3e_imsgev, needfd);
+	if (event_dispatch() < 0)
+		fatal("event_dispatch");
+
+	logit(LOG_INFO, "pop3d exiting");
+	return (0);
+}
+
+static void
+pop3e_imsgev(struct imsgev *iev, int code, struct imsg *imsg)
+{
+	switch (code) {
+	case IMSGEV_IMSG:
+		switch (imsg->hdr.type) {
+		case IMSG_AUTH:
+			authenticate(iev, imsg);
+			break;
+		default:
+			logit(LOG_DEBUG, "%s: unexpected imsg %u",
+			    __func__, imsg->hdr.type);
+			break;
+		}
+		break;
+	case IMSGEV_EREAD:
+	case IMSGEV_EWRITE:
+	case IMSGEV_EIMSG:
+		fatal("pop3d: imsgev read/write error");
+		break;
+	case IMSGEV_DONE:
+		event_loopexit(NULL);
+		break;
+	}
+}
+
+static void
+authenticate(struct imsgev *iev, struct imsg *imsg)
+{
+	struct auth_req	*req = imsg->data;
+	struct passwd	*pw;
+	int		pair[2];
+
+	if (auth_userokay(req->user, NULL, "auth-pop3", req->pass) == 0) {
+		logit(LOG_INFO, "%u: auth [%s] failed",
+		    imsg->hdr.peerid, req->user);
+		pair[0] = -1;
+		goto end;
+	}
+
+	logit(LOG_INFO, "%u: auth [%s] passed", imsg->hdr.peerid,
+	    req->user);
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) == -1)
+		fatal("socketpair");
+
+	set_nonblocking(pair[0]);
+	set_nonblocking(pair[1]);
+	if ((pw = getpwnam(req->user)) == NULL)
+		fatalx("authenticate: getpwnam");
+
+	maildrop_init(imsg->hdr.peerid, pair, pw, mtype, mpath);
+	close(pair[1]);
+
+end:
+	imsgev_xcompose(iev, IMSG_AUTH, imsg->hdr.peerid, 0,
+	    pair[0], NULL, 0, "authenticate");
+}
+
+static void
+needfd(struct imsgev *iev)
+{
+	fatalx("pop3d should never need an fd");
+}
+
+static void
+sig_handler(int sig, short event, void *arg)
+{
+	int status;
+
+	switch (sig) {
+	case SIGINT:
+	case SIGHUP:
+	case SIGTERM:
+		imsgev_clear(&iev_pop3e);
+		imsgev_close(&iev_pop3e);
+		event_loopexit(NULL);
+		break;
+	case SIGCHLD:
+		if (waitpid(pop3e_pid, &status, WNOHANG) > 0)
+			if (WIFEXITED(status) || WIFSIGNALED(status)) {
+				logit(LOG_ERR, "Lost pop3 engine");
+				event_loopexit(NULL);
+			}
+		break;
+	}
+}
+
+static enum m_type
+m_type(const char *str)
+{
+	if (strcasecmp(str, "mbox") == 0)
+		return M_MBOX;
+
+	if (strcasecmp(str, "maildir") == 0)
+		return M_MAILDIR;
+
+	return (-1);
+}
+
+static void
+usage(void)
+{
+	extern char *__progname;
+
+	fprintf(stderr, "usage: %s [-d] [-p path] [-t type]\n", __progname);
+	exit(EXIT_FAILURE);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pop3d.h	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/tree.h>
+
+#include <sha1.h>
+
+#include "imsgev.h"
+#include "iobuf.h"
+#include "ioev.h"
+
+#define	ARGLEN		40
+#define POP3S		0x01
+#define	F_DELE		0x01
+
+struct passwd;
+
+enum imsg_type {
+	IMSG_AUTH,
+	IMSG_MAILDROP_INIT,
+	IMSG_MAILDROP_RETR,
+	IMSG_MAILDROP_DELE,
+	IMSG_MAILDROP_RSET,
+	IMSG_MAILDROP_LIST,
+	IMSG_MAILDROP_LISTALL,
+	IMSG_MAILDROP_UIDLALL,
+	IMSG_MAILDROP_UPDATE
+};
+
+enum m_type {
+	M_MBOX,
+	M_MAILDIR
+};
+
+struct msg {
+	union {
+		SIMPLEQ_ENTRY(msg)	q_entry;
+		RB_ENTRY(msg)		t_entry;
+	}				e;
+	char				hash[SHA1_DIGEST_STRING_LENGTH];
+	size_t				sz;
+	size_t				nlines;
+	union {
+		long			offset;
+		const char		*fname;
+	}				u;
+	int				flags;
+};
+
+struct mdrop {
+	union {
+		SIMPLEQ_HEAD(, msg)	q_msgs;
+		RB_HEAD(msgtree, msg)	t_msgs;
+	}				e;
+	size_t				nmsgs;
+	size_t				sz;
+	struct msg			**msgs_index; /* random access to msgs */
+	int				fd;
+};
+
+struct stats {
+	size_t	nmsgs;
+	size_t	sz;
+};
+
+struct retr_req {
+	unsigned int	idx;
+	unsigned int	ntop;
+	int		top;
+};
+
+struct retr_res {
+	size_t		nlines;
+	long		offset;
+	unsigned int	ntop;
+	int		top;
+};
+
+struct list_req {
+	unsigned int	idx;
+	int		uidl;
+};
+
+struct list_res {
+	unsigned int	idx;
+	union {
+		size_t	sz;
+		char	hash[SHA1_DIGEST_STRING_LENGTH];
+	}		u;
+	int		uidl;
+};
+
+struct m_backend {
+	int (*init)(struct mdrop *, size_t *, size_t *);
+	int (*retr)(struct mdrop *, unsigned int, size_t *, size_t *);
+	int (*update)(struct mdrop *);
+};
+
+struct auth_req {
+	char	user[ARGLEN];
+	char	pass[ARGLEN];
+};
+
+struct listener {
+	struct sockaddr_storage	ss;
+	struct event		ev;
+	struct event		pause;
+	int			flags;
+	int			sock;
+};
+
+enum state {
+	AUTH,
+	TRANSACTION,
+	UPDATE
+};
+
+struct session {
+	SPLAY_ENTRY(session)	entry;
+	struct imsgev		iev_maildrop;
+	struct iobuf		iobuf;
+	struct io		io;
+	char			user[ARGLEN];
+	char			pass[ARGLEN];
+	size_t			m_sz;
+	size_t			nmsgs;
+	struct listener		*l;
+	uint32_t		id;
+	int			sock;
+	int			flags;
+	enum state		state;
+};
+
+/* pop3e.c */
+pid_t pop3_main(int [2], struct passwd *);
+
+/* session.c */
+void session_init(struct listener *, int);
+void session_close(struct session *, int);
+void session_reply(struct session *, char *, ...);
+void session_set_state(struct session *, enum state);
+void session_imsgev_init(struct session *, int);
+SPLAY_HEAD(session_tree, session);
+int session_cmp(struct session *, struct session *);
+SPLAY_PROTOTYPE(session_tree, session, entry, session_cmp);
+
+/* maildrop.c */
+void maildrop_init(uint32_t, int [2], struct passwd *,
+    int, const char *);
+
+/* util.c */
+void set_nonblocking(int);
+void log_init(int);
+void logit(int, const char *, ...);
+void vlog(int, const char *, va_list);
+void fatal(const char *);
+void fatalx(const char *);
+void *xcalloc(size_t, size_t, const char *);
+void iobuf_xfqueue(struct iobuf *, const char *, const char *, ...);
+void iobuf_xqueue(struct iobuf *, const char *, const void *, size_t);
+int imsgev_xcompose(struct imsgev *, u_int16_t, u_int32_t,
+    uint32_t, int, void *, u_int16_t, const char *);
+int get_index(struct session *, const char *, unsigned int *);
+void log_connect(uint32_t, struct sockaddr_storage *, socklen_t);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pop3e.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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/time.h>
+#include <sys/tree.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "imsgev.h"
+#include "pop3d.h"
+#include "ssl.h"
+
+#define BACKLOG		5
+
+static void auth_response(struct session *, int);
+static void pop3_accept(int, short, void *);
+static void pop3_listen(const char *);
+static void pop3_pause(int, short, void *);
+static void pop3d_imsgev(struct imsgev *, int, struct imsg *);
+static void needfd(struct imsgev *);
+static void sig_handler(int, short, void *);
+
+struct imsgev		iev_pop3d;
+void			*ssl_ctx;
+
+pid_t
+pop3_main(int pair[2], struct passwd *pw)
+{
+	extern struct session_tree	sessions;
+	struct event			ev_sigint, ev_sigterm;
+	pid_t				pid;
+
+	pid = fork();
+	if (pid < 0)
+		fatal("pop3e: fork");
+
+	if (pid > 0)
+		return (pid);
+
+	close(pair[0]);
+	setproctitle("pop3 engine");
+	SPLAY_INIT(&sessions);
+	event_init();
+	signal_set(&ev_sigint, SIGINT, sig_handler, NULL);
+	signal_set(&ev_sigterm, SIGTERM, sig_handler, NULL);
+	signal_add(&ev_sigint, NULL);
+	signal_add(&ev_sigterm, NULL);
+	imsgev_init(&iev_pop3d, pair[1], NULL, pop3d_imsgev, needfd);
+	pop3_listen("pop3");
+
+	ssl_init();
+	if ((ssl_ctx = ssl_setup()) == NULL)
+		fatal("ssl_setup failed");
+	pop3_listen("pop3s");
+
+	if (chroot(pw->pw_dir) == -1 || chdir("/") == -1)
+		fatal("chroot");
+
+	if (setgroups(1, &pw->pw_gid) ||
+	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+		fatal("cannot drop privileges");
+
+	if (event_dispatch() < 0)
+		fatal("event_dispatch");
+
+	logit(LOG_INFO, "pop3 engine exiting");
+	_exit(0);
+}
+
+static void
+pop3_listen(const char *port)
+{
+	struct listener	*l = NULL;
+	struct addrinfo	hints, *res, *res0;
+	int		error, opt, serrno, s;
+	const char	*cause = NULL;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = PF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags = AI_PASSIVE;
+	error = getaddrinfo(NULL, port, &hints, &res0);
+	if (error)
+		errx(1, "%s", gai_strerror(error));
+
+	for (res = res0; res != NULL; res = res->ai_next) {
+		s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (s == -1) {
+			cause = "socket";
+			continue;
+		}
+
+		opt = 1;
+		if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+		    &opt, sizeof(opt)) == -1)
+			fatal("listener setsockopt(SO_REUSEADDR)");
+
+		if (bind(s, res->ai_addr, res->ai_addrlen) == -1) {
+			serrno = errno;
+			cause = "bind";
+			close(s);
+			errno = serrno;
+			continue;
+		}
+
+		set_nonblocking(s);
+		if (listen(s, BACKLOG) == -1)
+			fatal("listen");
+
+		l = xcalloc(1, sizeof(*l), "pop3_listen");
+		l->sock = s;
+		if (strcmp(port, "pop3s") == 0)
+			l->flags |= POP3S;
+
+		event_set(&l->ev, s, EV_READ|EV_PERSIST, pop3_accept, l);
+		event_add(&l->ev, NULL);
+		evtimer_set(&l->pause, pop3_pause, l);
+	}
+
+	if (l == NULL)
+		errx(1, "%s", cause);
+
+	freeaddrinfo(res0);
+}
+
+static void
+pop3_accept(int fd, short events, void *arg)
+{
+	struct sockaddr_storage ss;
+	struct listener		*l = arg;
+	struct timeval		timeout = {1, 0};
+	socklen_t		len;
+	int			s;
+
+	len = sizeof(ss);
+	s = accept(fd, (struct sockaddr *)&ss, &len);
+	if (s == -1) {
+		switch (errno) {
+		case EINTR:
+		case EWOULDBLOCK:
+		case ECONNABORTED:
+			return;
+		case EMFILE:
+		case ENFILE:
+			event_del(&l->ev);
+			evtimer_add(&l->pause, &timeout);
+			return;
+		default:
+			fatalx("accept");
+		}
+	}
+
+	set_nonblocking(s);
+	l->ss = ss;
+	session_init(l, s);
+}
+
+static void
+pop3_pause(int fd, short events, void *arg)
+{
+	struct listener *l = arg;
+
+	event_add(&l->ev, NULL);
+}
+
+static void
+pop3d_imsgev(struct imsgev *iev, int code, struct imsg *imsg)
+{
+	extern struct session_tree	sessions;
+	struct session			key, *r;
+
+	switch (code) {
+	case IMSGEV_IMSG:
+		key.id = imsg->hdr.peerid;
+		r = SPLAY_FIND(session_tree, &sessions, &key);
+		if (r == NULL) {
+			logit(LOG_INFO, "%u: session not found", key.id);
+			fatalx("pop3e: session lost");
+		}
+		switch (imsg->hdr.type) {
+		case IMSG_AUTH:
+			auth_response(r, imsg->fd);
+			break;
+		default:
+			logit(LOG_DEBUG, "%s: unexpected imsg %d",
+			    __func__, imsg->hdr.type);
+			break;
+		}
+		break;
+	case IMSGEV_EREAD:
+	case IMSGEV_EWRITE:
+	case IMSGEV_EIMSG:
+		fatal("pop3e: imsgev read/write error");
+		break;
+	case IMSGEV_DONE:
+		event_loopexit(NULL);
+		break;
+	}
+}
+
+static void
+auth_response(struct session *s, int fd)
+{
+	if (fd == -1) {
+		session_reply(s, "%s", "-ERR auth failed");
+		io_set_write(&s->io);
+		session_close(s, 1);
+		return;
+	}
+
+	session_imsgev_init(s, fd);
+}
+
+static void
+needfd(struct imsgev *iev)
+{
+	/* XXX can anything be done to handle fd exhaustion? */
+	fatalx("pop3e needs an fd");
+}
+
+static void
+sig_handler(int sig, short event, void *arg)
+{
+	switch (sig) {
+	case SIGINT:
+	case SIGTERM:
+		event_loopexit(NULL);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/session.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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 <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "imsgev.h"
+#include "pop3d.h"
+#include "ssl.h"
+
+#define MAXLINESIZE	2048
+#define TIMEOUT		600000
+
+enum pop_command {
+	CMD_STLS = 0,
+	CMD_CAPA,
+	CMD_USER,
+	CMD_PASS,
+	CMD_QUIT,
+	CMD_STAT,
+	CMD_RETR,
+	CMD_LIST,
+	CMD_DELE,
+	CMD_RSET,
+	CMD_TOP,
+	CMD_UIDL,
+	CMD_NOOP
+};
+
+enum arg_constraint {
+	OPTIONAL = 1,
+	PROHIBITED,
+	REQUIRED
+};
+
+static struct {int code; enum arg_constraint c; const char *cmd;} commands[] = {
+	{CMD_STLS, PROHIBITED, "STLS"},
+	{CMD_CAPA, PROHIBITED, "CAPA"},
+	{CMD_USER, REQUIRED, "USER"},
+	{CMD_PASS, REQUIRED, "PASS"},
+	{CMD_QUIT, PROHIBITED, "QUIT"},
+	{CMD_STAT, PROHIBITED, "STAT"},
+	{CMD_RETR, REQUIRED, "RETR"},
+	{CMD_LIST, OPTIONAL, "LIST"},
+	{CMD_DELE, REQUIRED, "DELE"},
+	{CMD_RSET, PROHIBITED, "RSET"},
+	{CMD_TOP,  REQUIRED, "TOP"},
+	{CMD_UIDL, OPTIONAL, "UIDL"},
+	{CMD_NOOP, PROHIBITED, "NOOP"},
+	{-1, OPTIONAL, NULL}
+};
+
+static void auth_request(struct session *);
+static void capa(struct session *);
+static void command(struct session *, int, char *);
+static void session_io(struct io *, int);
+static void parse(struct session *, char *);
+static void auth_command(struct session *, int, char *);
+static void trans_command(struct session *, int, char *);
+static void get_list_all(struct session *, int);
+static void get_list(struct session *, unsigned int, int);
+static void maildrop_imsgev(struct imsgev *, int, struct imsg *);
+static void handle_init(struct session *, struct imsg *);
+static void handle_retr(struct session *, struct imsg *);
+static void handle_dele(struct session *, struct imsg *);
+static void handle_list(struct session *, struct imsg *);
+static void handle_list_all(struct session *, struct imsg *, int);
+static void handle_update(struct session *, struct imsg *);
+static void needfd(struct imsgev *);
+static void pop3_debug(char *, ...);
+static void session_write(struct session *, const char *, size_t);
+static const char *strstate(enum state);
+
+struct session_tree	sessions;
+static int		_pop3_debug = 1;
+
+void
+session_init(struct listener *l, int fd)
+{
+	struct session	*s;
+	void		*ssl;
+	extern void	*ssl_ctx;
+
+	s = xcalloc(1, sizeof(*s), "session_init");
+	s->l = l;
+	if (iobuf_init(&s->iobuf, 0, 0) == -1)
+		fatal("iobuf_init");
+
+	io_init(&s->io, fd, s, session_io, &s->iobuf);
+	io_set_timeout(&s->io, TIMEOUT);
+	s->id = arc4random();
+	s->sock = fd;
+	s->state = AUTH;
+	if (s->l->flags & POP3S) {
+		s->flags |= POP3S;
+		ssl = pop3s_init(ssl_ctx, s->sock);
+		io_set_read(&s->io);
+		io_start_tls(&s->io, ssl);
+		return;
+	}
+
+	log_connect(s->id, &l->ss, l->ss.ss_len);
+	SPLAY_INSERT(session_tree, &sessions, s);
+	session_reply(s, "%s", "+OK pop3d ready");
+	io_set_write(&s->io);
+}
+
+void
+session_close(struct session *s, int flush)
+{
+	struct session *entry;
+
+	entry = SPLAY_REMOVE(session_tree, &sessions,  s);
+	if (entry == NULL) {
+		 /* STARTTLS session was in progress and got interrupted */
+		logit(LOG_DEBUG, "%u: not in tree", s->id);
+		entry = s;
+	}
+
+	if (flush) {
+		if (entry->flags & POP3S)
+			iobuf_flush_ssl(&entry->iobuf, entry->io.ssl);
+		else
+			iobuf_flush(&entry->iobuf, entry->io.sock);
+	}
+
+	iobuf_clear(&entry->iobuf);
+	io_clear(&entry->io);
+	imsgev_clear(&entry->iev_maildrop);
+	imsgev_close(&entry->iev_maildrop);
+	logit(LOG_INFO, "%u: session closed", entry->id);
+	free(entry);
+}
+
+static void
+session_io(struct io *io, int evt)
+{
+	struct session	*s = io->arg;
+	char		*line;
+	size_t		len;
+
+	pop3_debug("%u: %s", s->id, io_strevent(evt));
+	switch (evt) {
+	case IO_DATAIN:
+		line = iobuf_getline(&s->iobuf, &len);
+		if (line == NULL) {
+			iobuf_normalize(&s->iobuf);
+			break;
+		}
+		if (strncasecmp(line, "PASS", 4) == 0)
+			pop3_debug(">>> PASS");
+		else
+			pop3_debug(">>> %s", line);
+		parse(s, line);
+		break;
+	case IO_LOWAT:
+		if (iobuf_queued(&s->iobuf) == 0)
+			io_set_read(io);
+		break;
+	case IO_TLSREADY:
+		/* greet only for pop3s, STLS already greeted */
+		if (s->flags & POP3S) {
+			log_connect(s->id, &s->l->ss, s->l->ss.ss_len);
+			session_reply(s, "%s", "+OK pop3 ready");
+			io_set_write(&s->io);
+		}
+		SPLAY_INSERT(session_tree, &sessions, s);
+		/* mark STLS session as secure */
+		s->flags |= POP3S;
+		logit(LOG_INFO, "%u: TLS ready", s->id);
+		break;
+	case IO_DISCONNECTED:
+	case IO_TIMEOUT:
+	case IO_ERROR:
+		session_close(s, 0);
+		break;
+	default:
+		logit(LOG_DEBUG, "unknown event %s", io_strevent(evt));
+		break;
+	}
+}
+
+static void
+parse(struct session *s, char *line)
+{
+	enum arg_constraint	c = OPTIONAL;
+	int			i, cmd = -1;
+	char			*args;
+
+	/* trim newline */
+	line[strcspn(line, "\n")] = '\0';
+
+	args = strchr(line, ' ');
+	if (args) {
+		*args++ = '\0';
+		while (isspace((unsigned char)*args))
+			args++;
+	}
+
+	for (i = 0; commands[i].code != -1; i++) {
+		if (strcasecmp(line, commands[i].cmd) == 0) {
+			cmd = commands[i].code;
+			c = commands[i].c;
+			break;
+		}
+	}
+
+	if (cmd == -1) {
+		logit(LOG_INFO, "%u: invalid command %s", s->id, line);
+		session_reply(s, "%s", "-ERR invalid command");
+		io_set_write(&s->io);
+		return;
+	}
+
+	if (c == PROHIBITED && args) {
+		session_reply(s, "%s", "-ERR no arguments allowed");
+		io_set_write(&s->io);
+		return;
+	} else if ((c == REQUIRED) &&
+	    (args == NULL || strlen(args) >= ARGLEN)) {
+		session_reply(s, "%s", "-ERR args required or too long");
+		io_set_write(&s->io);
+		return;
+	}
+
+	command(s, cmd, args);
+}
+
+static void
+command(struct session *s, int cmd, char *args)
+{
+	switch (s->state) {
+	case AUTH:
+		auth_command(s, cmd, args);
+		break;
+	case TRANSACTION:
+		trans_command(s, cmd, args);
+		break;
+	case UPDATE:
+		session_reply(s, "%s", "-ERR commands not allowed");
+		io_set_write(&s->io);
+		break;
+	default:
+		fatalx("Invalid state");
+	}
+}
+
+static void
+auth_command(struct session *s, int cmd, char *args)
+{
+	extern void	*ssl_ctx;
+	void		*ssl;
+
+	switch (cmd) {
+	case CMD_STLS:
+		if (s->flags & POP3S) {
+			session_reply(s, "%s", "-ERR already secured");
+			break;
+		}
+		session_reply(s, "%s", "+OK");
+		io_set_write(&s->io);
+		iobuf_flush(&s->iobuf, s->io.sock);
+		/* add back when IO_TLSREADY. */
+		SPLAY_REMOVE(session_tree, &sessions, s);
+		ssl = pop3s_init(ssl_ctx, s->sock);
+		io_set_read(&s->io);
+		io_start_tls(&s->io, ssl);
+		return;
+	case CMD_CAPA:
+		capa(s);
+		break;
+	case CMD_USER:
+		strlcpy(s->user, args, sizeof(s->user));
+		session_reply(s, "%s", "+OK");
+		break;
+	case CMD_PASS:
+		if (s->user[0] == '\0') {
+			session_reply(s, "%s", "-ERR no USER specified");
+			break;
+		}
+		strlcpy(s->pass, args, sizeof(s->pass));
+		auth_request(s);
+		return;
+	case CMD_QUIT:
+		session_reply(s, "%s", "+OK");
+		io_set_write(&s->io);
+		session_close(s, 1);
+		return;
+	default:
+		session_reply(s, "%s", "-ERR invalid command");
+		break;
+	}
+
+	io_set_write(&s->io);
+}
+
+static void
+auth_request(struct session *s)
+{
+	extern struct imsgev	iev_pop3d;
+	struct auth_req		req;
+
+	memset(&req, 0, sizeof(req));
+	strlcpy(req.user, s->user, sizeof(req.user));
+	strlcpy(req.pass, s->pass, sizeof(req.pass));
+	imsgev_xcompose(&iev_pop3d, IMSG_AUTH, s->id, 0, -1,
+	    &req, sizeof(req), "auth_request");
+}
+
+static void
+capa(struct session *s)
+{
+	session_reply(s, "%s", "+OK");
+	session_reply(s, "%s", "STLS");
+	session_reply(s, "%s", "USER");
+	session_reply(s, "%s", "TOP");
+	session_reply(s, "%s", "UIDL");
+	session_reply(s, "%s", "IMPLEMENTATION pop3d");
+	session_reply(s, "%s", ".");
+}
+
+static void
+trans_command(struct session *s, int cmd, char *args)
+{
+	struct retr_req	retr_req;
+	unsigned int	idx, n;
+	char		*c;
+	const char	*errstr;
+	int		uidl = 0;
+
+	memset(&retr_req, 0, sizeof(retr_req));
+	switch (cmd) {
+	case CMD_CAPA:
+		capa(s);
+		break;
+	case CMD_STAT:
+		session_reply(s, "%s %zu %zu", "+OK", s->nmsgs, s->m_sz);
+		break;
+	case CMD_TOP:
+		if ((c = strchr(args, ' ')) == NULL) {
+			session_reply(s, "%s", "-ERR invalid arguments");
+			break;
+		}
+		*c++ = '\0';
+		n = strtonum(c, 0, UINT_MAX, &errstr);
+		if (errstr) {
+			session_reply(s, "%s", "-ERR invalid n");
+			break;
+		}
+		retr_req.top = 1;
+		retr_req.ntop = n;
+		/* FALLTRHROUGH */
+	case CMD_RETR:
+		if (!get_index(s, args, &retr_req.idx))
+			break;
+		imsgev_xcompose(&s->iev_maildrop, IMSG_MAILDROP_RETR,
+		    s->id, 0, -1, &retr_req, sizeof(retr_req), "trans_command");
+		return;
+	case CMD_NOOP:
+		session_reply(s, "%s", "+OK");
+		break;
+	case CMD_DELE:
+		if (!get_index(s, args, &idx))
+			break;
+		imsgev_xcompose(&s->iev_maildrop, IMSG_MAILDROP_DELE,
+		    s->id, 0, -1, &idx, sizeof(idx), "trans_command");
+		return;
+	case CMD_RSET:
+		imsgev_xcompose(&s->iev_maildrop, IMSG_MAILDROP_RSET,
+		    s->id, 0, -1, NULL, 0, "trans_command");
+		return;
+	case CMD_UIDL:
+		uidl = 1;
+		/* FALLTHROUGH */
+	case CMD_LIST:
+		if (args) {
+			if (!get_index(s, args, &idx))
+				break;
+			get_list(s, idx, uidl);
+		} else
+			get_list_all(s, uidl);
+		return;
+	case CMD_QUIT:
+		imsgev_xcompose(&s->iev_maildrop, IMSG_MAILDROP_UPDATE,
+		    s->id, 0, -1, NULL, 0, "trans_command");
+		session_set_state(s, UPDATE);
+		return;
+	default:
+		session_reply(s, "%s", "-ERR invalid command");
+		break;
+	}
+
+	io_set_write(&s->io);
+}
+
+static void
+get_list_all(struct session *s, int uidl)
+{
+	imsgev_xcompose(&s->iev_maildrop,
+	    (uidl) ? IMSG_MAILDROP_UIDLALL : IMSG_MAILDROP_LISTALL,
+	    s->id, 0, -1, NULL, 0, "list_all");
+}
+
+static void
+get_list(struct session *s, unsigned int i, int uidl)
+{
+	struct list_req	req;
+
+	req.idx = i;
+	req.uidl = uidl;
+	imsgev_xcompose(&s->iev_maildrop, IMSG_MAILDROP_LIST,
+	    s->id, 0, -1, &req, sizeof(req), "list");
+}
+
+void
+session_imsgev_init(struct session *s, int fd)
+{
+	imsgev_init(&s->iev_maildrop, fd, s, maildrop_imsgev, needfd);
+}
+
+static void
+maildrop_imsgev(struct imsgev *iev, int code, struct imsg *imsg)
+{
+	struct session	key, *r;
+	int		uidl = 0;
+
+	switch (code) {
+	case IMSGEV_IMSG:
+		key.id = imsg->hdr.peerid;
+		r = SPLAY_FIND(session_tree, &sessions, &key);
+		if (r == NULL) {
+			logit(LOG_INFO, "%u: session not found", key.id);
+			fatalx("session: session lost");
+		}
+		switch (imsg->hdr.type) {
+		case IMSG_MAILDROP_INIT:
+			handle_init(r, imsg);
+			break;
+		case IMSG_MAILDROP_RETR:
+			handle_retr(r, imsg);
+			break;
+		case IMSG_MAILDROP_DELE:
+			handle_dele(r, imsg);
+			break;
+		case IMSG_MAILDROP_RSET:
+			session_reply(r, "%s", "+OK reset");
+			io_set_write(&r->io);
+			break;
+		case IMSG_MAILDROP_LIST:
+			handle_list(r, imsg);
+			break;
+		case IMSG_MAILDROP_UIDLALL:
+			uidl = 1;
+			/* FALLTHROUGH */
+		case IMSG_MAILDROP_LISTALL:
+			handle_list_all(r, imsg, uidl);
+			break;
+		case IMSG_MAILDROP_UPDATE:
+			handle_update(r, imsg);
+			break;
+		default:
+			logit(LOG_DEBUG, "%s: unexpected imsg %u",
+			    __func__, imsg->hdr.type);
+			break;
+		}
+		break;
+	case IMSGEV_EREAD:
+	case IMSGEV_EWRITE:
+	case IMSGEV_EIMSG:
+		fatal("session: imsgev read/write error");
+		break;
+	}
+}
+
+static void
+handle_init(struct session *s, struct imsg *imsg)
+{
+	size_t		datalen;
+	struct stats	*stats;
+
+	datalen = imsg->hdr.len - sizeof(imsg->hdr);
+	if (datalen) {
+		stats = imsg->data;
+		s->m_sz = stats->sz;
+		s->nmsgs = stats->nmsgs;
+		session_reply(s, "%s", "+OK maildrop ready");
+		io_set_write(&s->io);
+		session_set_state(s, TRANSACTION);
+	} else {
+		session_reply(s, "%s", "-ERR maildrop init failed");
+		io_set_write(&s->io);
+		session_close(s, 1);
+	}
+}
+
+static void
+handle_retr(struct session *s, struct imsg *imsg)
+{
+	struct retr_res	*r = imsg->data;
+	FILE		*fp;
+	char		*line;
+	size_t		len;
+
+	if (imsg->fd == -1) {
+		session_reply(s, "%s", "-ERR marked for delete");
+		io_set_write(&s->io);
+		return;
+	}
+
+	if ((fp = fdopen(imsg->fd, "r")) == NULL) {
+		logit(LOG_INFO, "%zu: retr failed", s->id);
+		session_reply(s, "%s", "-ERR RETR failed");
+		io_set_write(&s->io);
+		session_close(s, 1);
+		return;
+	}
+
+	if (fseek(fp, r->offset, SEEK_SET) == -1)
+		fatal("fseek");
+
+	session_reply(s, "%s", "+OK");
+	/* Ignore "From " line when type is mbox; maildir doesn't have it */
+	if ((line = fgetln(fp, &len)) && strncmp(line, "From ", 5))
+		session_write(s, line, len);
+
+	if (r->top) {
+		/* print headers regardless of ntop */
+		while ((line = fgetln(fp, &len))) {
+			session_write(s, line, len);
+			r->nlines -= 1;
+			if (strncmp(line , "\n", 1) == 0)
+				break;
+		}
+
+		/* print ntop lines of body */
+		while ((r->ntop-- > 0) && r->nlines-- &&
+		    (line = fgetln(fp, &len)))
+			session_write(s, line, len);
+	} else
+		while (r->nlines-- && (line = fgetln(fp, &len)))
+			session_write(s, line, len);
+
+	session_reply(s, "%s", ".");
+	io_set_write(&s->io);
+	fclose(fp);
+	close(imsg->fd);
+}
+
+static void
+handle_dele(struct session *s, struct imsg *imsg)
+{
+	int	*res = imsg->data;
+
+	if (*res == 0)
+		session_reply(s, "%s", "+OK marked for delete");
+	else
+		session_reply(s, "%s", "+ERR msg already marked delete");
+
+	io_set_write(&s->io);
+}
+
+/* DELEted msg's hash and sz will be zero, ignore them */
+static void
+handle_list(struct session *s, struct imsg *imsg)
+{
+	struct list_res	*res = imsg->data;
+
+	res->idx += 1;	/* POP3 index is 1 based */
+	if (res->uidl) {
+		if (strlen(res->u.hash))
+			session_reply(s, "+OK %zu %s", res->idx, res->u.hash);
+		else
+			session_reply(s, "-ERR marked for delete");
+	} else {
+		if (res->u.sz)
+			session_reply(s, "+OK %zu %zu", res->idx, res->u.sz);
+		else
+			session_reply(s, "-ERR marked for delete");
+	}
+
+	io_set_write(&s->io);
+}
+
+/* DELEted msg's hash and sz will be zero, ignore them */
+static void
+handle_list_all(struct session *s, struct imsg *imsg, int uidl)
+{
+	char 	*nhash = NULL;
+	size_t	datalen, i, item_sz, j, nitems, *nsz = NULL;
+
+	datalen = imsg->hdr.len - sizeof(imsg->hdr);
+	item_sz = (uidl) ? SHA1_DIGEST_STRING_LENGTH : sizeof(size_t);
+	nitems = datalen / item_sz;
+	if (uidl)
+		nhash = imsg->data;
+	else
+		nsz = imsg->data;
+
+	session_reply(s, "+OK");
+	for (i = 0; i < nitems; i++) {
+		if (uidl) {
+			j = i * SHA1_DIGEST_STRING_LENGTH;
+			if (nhash[j])
+				session_reply(s, "%zu %s", i + 1, nhash + j);
+		} else {
+			if (nsz[i])
+				session_reply(s, "%zu %zu", i + 1, nsz[i]);
+		}
+	}
+
+	session_reply(s, ".");
+	io_set_write(&s->io);
+
+}
+
+static void
+handle_update(struct session *s, struct imsg *imsg)
+{
+	int	*res = imsg->data;
+
+	if (*res == 0)
+		session_reply(s, "%s", "+OK maildrop updated");
+	else
+		session_reply(s, "%s", "-ERR maildrop update failed");
+
+	io_set_write(&s->io);
+	session_close(s, 1);
+}
+
+static void
+needfd(struct imsgev *iev)
+{
+	/* XXX */
+	fatalx("session needs an fd");
+}
+
+int
+session_cmp(struct session *a, struct session *b)
+{
+	if (a->id < b->id)
+		return (-1);
+
+	if (a->id > b->id)
+		return (1);
+
+	return (0);
+}
+
+void
+session_set_state(struct session *s, enum state newstate)
+{
+	pop3_debug("%u: %s -> %s", s->id, strstate(s->state),
+	    strstate(newstate));
+	s->state = newstate;
+}
+
+#define CASE(x) case x : return #x
+static const char *
+strstate(enum state state)
+{
+	static char buf[32];
+
+	switch (state) {
+	CASE(AUTH);
+	CASE(TRANSACTION);
+	CASE(UPDATE);
+	default:
+		snprintf(buf, sizeof(buf), "%d ???", state);
+		return (buf);
+	}
+}
+
+void
+session_reply(struct session *s, char *fmt, ...)
+{
+	va_list	ap;
+	int	n;
+	char	buf[MAXLINESIZE];
+
+	va_start(ap, fmt);
+	n = vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+	if (n == -1 || n > MAXLINESIZE)
+		fatalx("session_reply: response too long");
+
+	if (buf[0] == '+')
+		pop3_debug("<<< +OK");
+	else if (buf[0] == '-')
+		pop3_debug("<<< -ERR");
+
+	iobuf_xfqueue(&s->iobuf, "session_reply", "%s\r\n", buf);
+}
+
+static void
+session_write(struct session *s, const char *data, size_t len)
+{
+	/* remove terminating \n or \r\n if any */
+	if (data[len - 1] == '\n')
+		len -= 1;
+
+	if (data[len - 1] == '\r')
+		len -= 1;
+
+	/* byte stuff "." if at beginning of line */
+	if (data[0] == '.')
+		iobuf_xfqueue(&s->iobuf, "session_write", ".");
+
+	iobuf_xqueue(&s->iobuf, "session_write", data, len);
+	/* explicitly terminate with CRLF */
+	iobuf_xfqueue(&s->iobuf, "session_write", "\r\n");
+}
+
+static void
+pop3_debug(char *fmt, ...)
+{
+	va_list		ap;
+	char		buf[MAXLINESIZE];
+	int		n;
+
+	if (!_pop3_debug)
+		return;
+
+	va_start(ap, fmt);
+	n = vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+	if (n == -1 || n > MAXLINESIZE)
+		fatalx("pop3_debug: response too long");
+
+	logit(LOG_DEBUG, "%s", buf);
+}
+
+SPLAY_GENERATE(session_tree, session, entry, session_cmp);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ssl.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2013 Sunil Nimmagadda <sunil@nimmagadda.net>
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@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 <sys/stat.h>
+
+#include <fcntl.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+
+#include "pop3d.h"
+#include "ssl.h"
+
+#define SSL_CIPHERS		"HIGH"
+#define SSL_SESSION_TIMEOUT	300
+#define CERTFILE		"/etc/ssl/server.crt"
+#define KEYFILE			"/etc/ssl/private/server.key"
+
+static char *ssl_load_file(const char *, off_t *);
+
+void
+ssl_init(void)
+{
+	/* SSL init */
+	SSL_library_init();
+	SSL_load_error_strings();
+	OpenSSL_add_all_algorithms();
+
+	/* Init hardware cryto engines. */
+	ENGINE_load_builtin_engines();
+	ENGINE_register_all_complete();
+}
+
+void *
+ssl_setup(void)
+{
+	SSL_CTX *ctx = NULL;
+	char	*cert, *key;
+	off_t	cert_len, key_len;
+
+	/* SSL context creation */
+	ctx = SSL_CTX_new(SSLv23_server_method());
+	if (ctx == NULL) {
+		ssl_error("ssl_ctx_create");
+		fatal("ssl_ctx_create: could not create SSL context");
+	}
+
+	SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+	SSL_CTX_set_timeout(ctx, SSL_SESSION_TIMEOUT);
+	SSL_CTX_set_options(ctx,
+	    SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_TICKET);
+	SSL_CTX_set_options(ctx,
+	    SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+	/* SSL certificate, key loading */
+	cert = ssl_load_file(CERTFILE, &cert_len);
+	if (cert == NULL)
+		fatal("ssl_load_file: Unable to load " CERTFILE);
+
+	key = ssl_load_file(KEYFILE, &key_len);
+	if (key == NULL)
+		fatal("ssl_load_file: Unable to load " KEYFILE);
+
+	if (!SSL_CTX_set_cipher_list(ctx, SSL_CIPHERS))
+		goto err;
+
+	if (!ssl_ctx_use_certificate_chain(ctx, cert, cert_len))
+		goto err;
+
+	else if (!ssl_ctx_use_private_key(ctx, key, key_len))
+		goto err;
+
+	else if (!SSL_CTX_check_private_key(ctx))
+		goto err;
+
+	return (ctx);
+
+err:
+	if (ctx != NULL)
+		SSL_CTX_free(ctx);
+	ssl_error("ssl_setup");
+	fatal("ssl_setup: cannot set SSL up");
+	return (NULL);
+}
+
+void *
+pop3s_init(SSL_CTX *ctx, int fd)
+{
+	SSL *ssl;
+
+	if ((ssl = SSL_new(ctx)) == NULL)
+		fatal("SSL_new");
+
+	if (SSL_set_fd(ssl, fd) == 0)
+		fatal("SSL_set_fd");
+
+	return (ssl);
+}
+
+static char *
+ssl_load_file(const char *name, off_t *len)
+{
+	struct stat	st;
+	off_t		size;
+	char		*buf = NULL;
+	int		fd;
+
+	if ((fd = open(name, O_RDONLY)) == -1)
+		return (NULL);
+
+	if (fstat(fd, &st) != 0)
+		goto fail;
+
+	size = st.st_size;
+	if ((buf = calloc(1, size + 1)) == NULL)
+		goto fail;
+	if (read(fd, buf, size) != size)
+		goto fail;
+
+	close(fd);
+
+	*len = size;
+	return (buf);
+
+fail:
+	if (buf != NULL)
+		free(buf);
+
+	close(fd);
+	return (NULL);
+}
+
+void
+ssl_error(const char *where)
+{
+	unsigned long	code;
+	char		errbuf[128];
+	extern int	debug;
+
+	if (!debug)
+		return;
+
+	for (; (code = ERR_get_error()) != 0 ;) {
+		ERR_error_string_n(code, errbuf, sizeof(errbuf));
+		logit(LOG_DEBUG, "SSL library error: %s: %s", where, errbuf);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ssl.h	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,11 @@
+#include <openssl/ssl.h>
+
+/* ssl.c */
+void ssl_init(void);
+void *ssl_setup(void);
+void *pop3s_init(SSL_CTX *, int);
+void ssl_error(const char *);
+
+/* ssl_privsep.c */
+int ssl_ctx_use_private_key(SSL_CTX *, char *, off_t);
+int ssl_ctx_use_certificate_chain(SSL_CTX *, char *, off_t);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ssl_privsep.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,253 @@
+/*      $OpenBSD: ssl_privsep.c,v 1.1 2014/01/27 15:49:52 sunil Exp $    */
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ *
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ *
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
+/*
+ * SSL operations needed when running in a privilege separated environment.
+ * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard .
+ */
+
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include <unistd.h>
+#include <stdio.h>
+
+#include <openssl/err.h>
+#include <openssl/bio.h>
+#include <openssl/objects.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+
+int	 ssl_ctx_use_private_key(SSL_CTX *, char *, off_t);
+int	 ssl_ctx_use_certificate_chain(SSL_CTX *, char *, off_t);
+int	 ssl_ctx_load_verify_memory(SSL_CTX *, char *, off_t);
+int	 ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **);
+
+X509_LOOKUP_METHOD x509_mem_lookup = {
+	"Load cert from memory",
+	NULL,			/* new */
+	NULL,			/* free */
+	NULL,			/* init */
+	NULL,			/* shutdown */
+	ssl_by_mem_ctrl,	/* ctrl */
+	NULL,			/* get_by_subject */
+	NULL,			/* get_by_issuer_serial */
+	NULL,			/* get_by_fingerprint */
+	NULL,			/* get_by_alias */
+};
+
+#define X509_L_ADD_MEM	3
+
+int
+ssl_ctx_use_private_key(SSL_CTX *ctx, char *buf, off_t len)
+{
+	int		 ret;
+	BIO		*in;
+	EVP_PKEY	*pkey;
+
+	ret = 0;
+
+	if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
+		SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB);
+		return 0;
+	}
+
+	pkey = PEM_read_bio_PrivateKey(in, NULL,
+	    ctx->default_passwd_callback,
+	    ctx->default_passwd_callback_userdata);
+
+	if (pkey == NULL) {
+		SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB);
+		goto end;
+	}
+	ret = SSL_CTX_use_PrivateKey(ctx, pkey);
+	EVP_PKEY_free(pkey);
+end:
+	if (in != NULL)
+		BIO_free(in);
+	return ret;
+}
+
+
+int
+ssl_ctx_use_certificate_chain(SSL_CTX *ctx, char *buf, off_t len)
+{
+	int		 ret;
+	BIO		*in;
+	X509		*x;
+	X509		*ca;
+	unsigned long	 err;
+
+	ret = 0;
+	x = ca = NULL;
+
+	if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
+		SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB);
+		goto end;
+	}
+
+	if ((x = PEM_read_bio_X509(in, NULL,
+	    ctx->default_passwd_callback,
+	    ctx->default_passwd_callback_userdata)) == NULL) {
+		SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB);
+		goto end;
+	}
+
+	if (!SSL_CTX_use_certificate(ctx, x) || ERR_peek_error() != 0)
+		goto end;
+
+	/* If we could set up our certificate, now proceed to
+	 * the CA certificates.
+	 */
+
+	if (ctx->extra_certs != NULL) {
+		sk_X509_pop_free(ctx->extra_certs, X509_free);
+		ctx->extra_certs = NULL;
+	}
+
+	while ((ca = PEM_read_bio_X509(in, NULL,
+	    ctx->default_passwd_callback,
+	    ctx->default_passwd_callback_userdata)) != NULL) {
+
+		if (!SSL_CTX_add_extra_chain_cert(ctx, ca))
+			goto end;
+	}
+
+	err = ERR_peek_last_error();
+	if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
+	    ERR_GET_REASON(err) == PEM_R_NO_START_LINE)
+		ERR_clear_error();
+	else
+		goto end;
+
+	ret = 1;
+end:
+	if (ca != NULL)
+		X509_free(ca);
+	if (x != NULL)
+		X509_free(x);
+	if (in != NULL)
+		BIO_free(in);
+	return (ret);
+}
+
+int
+ssl_ctx_load_verify_memory(SSL_CTX *ctx, char *buf, off_t len)
+{
+	X509_LOOKUP		*lu;
+	struct iovec		 iov;
+
+	if ((lu = X509_STORE_add_lookup(ctx->cert_store,
+	    &x509_mem_lookup)) == NULL)
+		return (0);
+
+	iov.iov_base = buf;
+	iov.iov_len = len;
+
+	if (!ssl_by_mem_ctrl(lu, X509_L_ADD_MEM,
+	    (const char *)&iov, X509_FILETYPE_PEM, NULL))
+		return (0);
+
+	return (1);
+}
+
+int
+ssl_by_mem_ctrl(X509_LOOKUP *lu, int cmd, const char *buf,
+    long type, char **ret)
+{
+	STACK_OF(X509_INFO)	*inf;
+	const struct iovec	*iov;
+	X509_INFO		*itmp;
+	BIO			*in = NULL;
+	int			 i, count = 0;
+
+	iov = (const struct iovec *)buf;
+
+	if (type != X509_FILETYPE_PEM)
+		goto done;
+
+	if ((in = BIO_new_mem_buf(iov->iov_base, iov->iov_len)) == NULL)
+		goto done;
+
+	if ((inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL)) == NULL)
+		goto done;
+
+	for (i = 0; i < sk_X509_INFO_num(inf); i++) {
+		itmp = sk_X509_INFO_value(inf, i);
+		if (itmp->x509) {
+			X509_STORE_add_cert(lu->store_ctx, itmp->x509);
+			count++;
+		}
+		if (itmp->crl) {
+			X509_STORE_add_crl(lu->store_ctx, itmp->crl);
+			count++;
+		}
+	}
+	sk_X509_INFO_pop_free(inf, X509_INFO_free);
+
+ done:
+	if (!count)
+		X509err(X509_F_X509_LOAD_CERT_CRL_FILE,ERR_R_PEM_LIB);
+
+	if (in != NULL)
+		BIO_free(in);
+	return (count);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/util.c	Thu Mar 27 09:53:52 2014 +0500
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2014 Sunil Nimmagadda <sunil@nimmagadda.net>
+ *
+ * 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "pop3d.h"
+
+int debug = 0;
+
+void
+set_nonblocking(int fd)
+{
+	int	 flags;
+
+	if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+		fatal("fcntl F_GETFL");
+
+	flags |= O_NONBLOCK;
+	if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+		fatal("fcntl F_SETFL");
+}
+
+void
+log_init(int n_debug)
+{
+	extern char *__progname;
+
+	debug = n_debug;
+	if (!debug)
+		openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+	tzset();
+}
+
+void
+fatal(const char *emsg)
+{
+	if (errno)
+		logit(LOG_CRIT, "fatal: %s: %s\n", emsg, strerror(errno));
+	else
+		logit(LOG_CRIT, "fatal: %s\n", emsg);
+
+	exit(EXIT_FAILURE);
+}
+
+void
+fatalx(const char *emsg)
+{
+	errno = 0;
+	fatal(emsg);
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	vlog(pri, fmt, ap);
+	va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+	char *nfmt;
+
+	if (debug) {
+		/* best effort in out of mem situations */
+		if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+			vfprintf(stderr, fmt, ap);
+			fprintf(stderr, "\n");
+		} else {
+			vfprintf(stderr, nfmt, ap);
+			free(nfmt);
+		}
+		fflush(stderr);
+	} else
+		vsyslog(pri, fmt, ap);
+}
+
+void *
+xcalloc(size_t nmemb, size_t size, const char *where)
+{
+	void	*r;
+
+	if ((r = calloc(nmemb, size)) == NULL) {
+		logit(LOG_CRIT, "%s: calloc(%zu, %zu)", where, nmemb, size);
+		err(1, "exiting");
+	}
+
+	return (r);
+}
+
+int
+imsgev_xcompose(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+    uint32_t pid, int fd, void *data, u_int16_t datalen, const char *where)
+{
+	int	r;
+	r = imsgev_compose(iev, type, peerid, pid, fd, data, datalen);
+	if (r == -1) {
+		logit(LOG_CRIT, "imsgev_xcompose: %s", where);
+		errx(1, "maildrop exiting");
+	}
+
+	return (r);
+}
+
+void
+iobuf_xfqueue(struct iobuf *io, const char *where, const char *fmt, ...)
+{
+	va_list	ap;
+	int	len;
+
+	va_start(ap, fmt);
+	len = iobuf_vfqueue(io, fmt, ap);
+	va_end(ap);
+
+	if (len == -1)
+		errx(1, "%s: iobuf_xfqueue(%p, %s, ...)", where, io, fmt);
+}
+
+void
+iobuf_xqueue(struct iobuf *io, const char *where, const void *data, size_t len)
+{
+	if (iobuf_queue(io, data, len) == -1)
+		errx(1, "%s: iobuf_xqueue(%p, data, %zu)", where, io, len);
+}
+
+int
+get_index(struct session *s, const char *args, unsigned int *idx)
+{
+	const char	*errstr;
+
+	*idx = strtonum(args, 1, UINT_MAX, &errstr);
+	if (errstr || *idx < 1 || *idx > s->nmsgs) {
+		logit(LOG_INFO, "%zu: Invalid index", s->id);
+		session_reply(s, "%s", "-ERR invalid index");
+		return (0);
+	}
+
+	*idx -= 1; /* make it zero based */
+	return (1);
+}
+
+void
+log_connect(uint32_t id, struct sockaddr_storage *s, socklen_t s_len)
+{
+	char	hbuf[NI_MAXHOST];
+	int	e;
+
+	e = getnameinfo((struct sockaddr *)s, s_len, hbuf, sizeof(hbuf),
+	    NULL, 0, NI_NUMERICHOST);
+	if (e) {
+		logit(LOG_DEBUG, "getnameinfo: %s", gai_strerror(e));
+		logit(LOG_INFO, "new session with id %u", id);
+	} else
+		logit(LOG_INFO, "new session with id %u from %s", id, hbuf);
+}