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