summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile14
-rw-r--r--imsgev.c170
-rw-r--r--imsgev.h49
-rw-r--r--iobuf.c467
-rw-r--r--iobuf.h71
-rw-r--r--ioev.c910
-rw-r--r--ioev.h69
-rw-r--r--maildir.c229
-rw-r--r--maildrop.c412
-rw-r--r--mbox.c206
-rw-r--r--pop3d.886
-rw-r--r--pop3d.c230
-rw-r--r--pop3d.h177
-rw-r--r--pop3e.c255
-rw-r--r--session.c754
-rw-r--r--ssl.c168
-rw-r--r--ssl.h11
-rw-r--r--ssl_privsep.c253
-rw-r--r--util.c183
20 files changed, 4717 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7495889
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+CVS/
+obj/
+tags
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..58e4f80
--- /dev/null
+++ b/Makefile
@@ -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>
diff --git a/imsgev.c b/imsgev.c
new file mode 100644
index 0000000..6b92d79
--- /dev/null
+++ b/imsgev.c
@@ -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);
+}
diff --git a/imsgev.h b/imsgev.h
new file mode 100644
index 0000000..d429c50
--- /dev/null
+++ b/imsgev.h
@@ -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__ */
diff --git a/iobuf.c b/iobuf.c
new file mode 100644
index 0000000..58f452d
--- /dev/null
+++ b/iobuf.c
@@ -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 */
diff --git a/iobuf.h b/iobuf.h
new file mode 100644
index 0000000..7c5c85c
--- /dev/null
+++ b/iobuf.h
@@ -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 *);
diff --git a/ioev.c b/ioev.c
new file mode 100644
index 0000000..5ed5148
--- /dev/null
+++ b/ioev.c
@@ -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 */
diff --git a/ioev.h b/ioev.h
new file mode 100644
index 0000000..3353013
--- /dev/null
+++ b/ioev.h
@@ -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);
diff --git a/maildir.c b/maildir.c
new file mode 100644
index 0000000..f3d7178
--- /dev/null
+++ b/maildir.c
@@ -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);
diff --git a/maildrop.c b/maildrop.c
new file mode 100644
index 0000000..634fa3f
--- /dev/null
+++ b/maildrop.c
@@ -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);
+}
+
diff --git a/mbox.c b/mbox.c
new file mode 100644
index 0000000..83b3d80
--- /dev/null
+++ b/mbox.c
@@ -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);
+}
+
diff --git a/pop3d.8 b/pop3d.8
new file mode 100644
index 0000000..344316c
--- /dev/null
+++ b/pop3d.8
@@ -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.
diff --git a/pop3d.c b/pop3d.c
new file mode 100644
index 0000000..56c8047
--- /dev/null
+++ b/pop3d.c
@@ -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);
+}
+
diff --git a/pop3d.h b/pop3d.h
new file mode 100644
index 0000000..3035795
--- /dev/null
+++ b/pop3d.h
@@ -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);
diff --git a/pop3e.c b/pop3e.c
new file mode 100644
index 0000000..cb6f017
--- /dev/null
+++ b/pop3e.c
@@ -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);
+ }
+}
+
diff --git a/session.c b/session.c
new file mode 100644
index 0000000..77402a3
--- /dev/null
+++ b/session.c
@@ -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);
+
diff --git a/ssl.c b/ssl.c
new file mode 100644
index 0000000..fce300f
--- /dev/null
+++ b/ssl.c
@@ -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);
+ }
+}
+
diff --git a/ssl.h b/ssl.h
new file mode 100644
index 0000000..b52d626
--- /dev/null
+++ b/ssl.h
@@ -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);
diff --git a/ssl_privsep.c b/ssl_privsep.c
new file mode 100644
index 0000000..e996769
--- /dev/null
+++ b/ssl_privsep.c
@@ -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);
+}
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..4a95a1d
--- /dev/null
+++ b/util.c
@@ -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);
+}