summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/smtpd/ARCHITECTURE105
-rw-r--r--usr.sbin/smtpd/Makefile21
-rw-r--r--usr.sbin/smtpd/aliases.c447
-rw-r--r--usr.sbin/smtpd/atomic.c97
-rw-r--r--usr.sbin/smtpd/buffer.c247
-rw-r--r--usr.sbin/smtpd/config.c181
-rw-r--r--usr.sbin/smtpd/control.c474
-rw-r--r--usr.sbin/smtpd/debug.c114
-rw-r--r--usr.sbin/smtpd/dns.c189
-rw-r--r--usr.sbin/smtpd/forward.c118
-rw-r--r--usr.sbin/smtpd/imsg.c317
-rw-r--r--usr.sbin/smtpd/lka.c1013
-rw-r--r--usr.sbin/smtpd/log.c161
-rw-r--r--usr.sbin/smtpd/map.c63
-rw-r--r--usr.sbin/smtpd/mda.c390
-rw-r--r--usr.sbin/smtpd/mfa.c356
-rw-r--r--usr.sbin/smtpd/mta.c786
-rw-r--r--usr.sbin/smtpd/parse.y1377
-rw-r--r--usr.sbin/smtpd/queue.c1315
-rw-r--r--usr.sbin/smtpd/smtp.c586
-rw-r--r--usr.sbin/smtpd/smtp_session.c955
-rw-r--r--usr.sbin/smtpd/smtpd.899
-rw-r--r--usr.sbin/smtpd/smtpd.c1005
-rw-r--r--usr.sbin/smtpd/smtpd.conf11
-rw-r--r--usr.sbin/smtpd/smtpd.conf.5101
-rw-r--r--usr.sbin/smtpd/smtpd.h745
-rw-r--r--usr.sbin/smtpd/ssl.c496
-rw-r--r--usr.sbin/smtpd/ssl_privsep.c170
-rw-r--r--usr.sbin/smtpd/store.c265
29 files changed, 12204 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/ARCHITECTURE b/usr.sbin/smtpd/ARCHITECTURE
new file mode 100644
index 00000000000..608d7d521a6
--- /dev/null
+++ b/usr.sbin/smtpd/ARCHITECTURE
@@ -0,0 +1,105 @@
++==============================+
+|The SMTP Daemon Architecture |
++==============================+
+
+Introduction
+------------
+
+The SMTP daemon is a complete mail architecture designed to provide
+an MTA and MDA for OpenBSD systems (and others !)
+
+The SMTP daemon is configurable through a simple pf-like syntax to
+select external or local delivery and the means to do so.
+
+The SMTP Daemon relies on a fully asynchronous and event based data
+transfer model. The IMSG pseudo-protocol is used throughout the daemon
+to provide communications between separate privilege-separated
+processes.
+
+The following processes are currently implemented:
+
+ * smtp: The unpriviledged SMTP server
+ * mfa: Mail filter agent
+ * queue: A queue scheduler
+ * mda: Local mail delivery agent
+ * mta: Remote mail delivery agent
+ * lka: Lookup agent, handles DNS, db, file and external maps
+ * control: External interaction gateway
+
+The SMTP Server
+---------------
+
+The SMTP Server conforms RFC 5321 and also provides interesting
+extensions such as STARTTLS (RFC 2487).
+
+
+The Mail Filter Agent
+---------------------
+
+The mail filter agent has two roles. First it decides if a mail is
+allowed to be delivered by the daemon, it then performs resolution
+to determine the delivery method that applies to the mail, either
+local or remote. The second duty is to perform optionnal modifications
+on the envelope content.
+
+
+WorkFlow
+--------
+
+The base structure that traverses most of smtpd's architecture is a
+struct msg, the typical workflow is as show below from left to right:
+
++------+ +-----+
+| SMTP |======+ +===| MDA |
++------+ | +-----+ +-------+ | +-----+
+ |=====| MFA |=====| Queue |=====|
+ +-----+ +-------+ | +-----+
+ || +===| MTA |
+ +-----+ +-----+
+ | LKA |
+ +-----+
+
+
+Client -> Server workflow:
+
+1- The message is initially received from either the listening service or
+ /usr/sbin/sendmail (for which smtpctl is a link).
+
+2- As envelope is gathered, the information is handed out to MFA for
+ validation.
+
+3- MFA takes decision as to wether or not the message can be delivered
+ locally or relayed out and notifies SMTP of its decision.
+
+4- If message is not rejected, a file descriptor is requested from QUEUE
+ by SMTP which will then write the message to the file descriptor.
+
+5- Once content is written, SMTP notifies QUEUE that the content is
+ fully written and provides a list of recipients.
+
+6- QUEUE constructs batches of messages with same message id and going
+ to same destination, and registers them to the batch queue. It then
+ writes the information on-disk so that it can recovert from a crash
+ or shutdown, and notifies SMTP that message is accepted.
+
+7- SMTP notifies client that message was accepted.
+
+
+Queue workflow:
+
+1- A batch queue traversal is done. For each batch, QUEUE computes the
+ retry time based on the number of times it was tried already and
+ the time of last attempt. A batch may be in three states:
+
+ a) it is either ready for processing, in which case it is
+ handed to MDA or MTA for delivery attempt.
+
+ b) the delay before next retry has not yet expired, the
+ scheduler will simply ignore it.
+
+ c) the batch age (currently 4 days) has expired, the
+ scheduler will eventually generate a mailer daemon batch
+ and remove the original batch from the queue.
+
+
+XXX - needs to be completed/improved/updated
diff --git a/usr.sbin/smtpd/Makefile b/usr.sbin/smtpd/Makefile
new file mode 100644
index 00000000000..b59093c6236
--- /dev/null
+++ b/usr.sbin/smtpd/Makefile
@@ -0,0 +1,21 @@
+# $OpenBSD: Makefile,v 1.1 2008/11/01 21:35:28 gilles Exp $
+
+PROG= smtpd
+SRCS= parse.y atomic.c log.c config.c buffer.c imsg.c \
+ smtpd.c lka.c mfa.c queue.c mta.c mda.c control.c \
+ smtp.c smtp_session.c store.c debug.c \
+ ssl.c ssl_privsep.c dns.c aliases.c forward.c \
+ map.c
+MAN= smtpd.8 smtpd.conf.5
+
+LDADD= -levent -lutil -lssl -lcrypto
+DPADD= ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO}
+CFLAGS= -g3 -ggdb -I${.CURDIR}
+CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual
+CFLAGS+= -Wsign-compare -Wbounded
+#CFLAGS+= -Werror # during development phase (breaks some archs)
+YFLAGS=
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/smtpd/aliases.c b/usr.sbin/smtpd/aliases.c
new file mode 100644
index 00000000000..2e66476d484
--- /dev/null
+++ b/usr.sbin/smtpd/aliases.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "smtpd.h"
+
+int aliases_exist(struct smtpd *, char *);
+int aliases_get(struct smtpd *, struct aliaseslist *, char *);
+int aliases_virtual_exist(struct smtpd *, struct path *);
+int aliases_virtual_get(struct smtpd *, struct aliaseslist *, struct path *);
+int aliases_expand_include(struct aliaseslist *, char *);
+
+int alias_parse(struct alias *, char *);
+int alias_is_filter(struct alias *, char *, size_t);
+int alias_is_username(struct alias *, char *, size_t);
+int alias_is_address(struct alias *, char *, size_t);
+int alias_is_filename(struct alias *, char *, size_t);
+int alias_is_include(struct alias *, char *, size_t);
+
+int
+aliases_exist(struct smtpd *env, char *username)
+{
+ int ret;
+ DBT key;
+ DBT val;
+ DB *aliasesdb;
+ struct map *map;
+
+ map = map_findbyname(env, "aliases");
+ if (map == NULL)
+ return 0;
+
+ if (map->m_src != S_DB) {
+ log_info("map source for \"aliases\" must be \"db\".");
+ return 0;
+ }
+
+ aliasesdb = dbopen(map->m_config, O_RDONLY, 0600, DB_HASH, NULL);
+ if (aliasesdb == NULL)
+ return 0;
+
+ key.data = username;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) == -1) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+ aliasesdb->close(aliasesdb);
+
+ return ret == 0 ? 1 : 0;
+}
+
+int
+aliases_get(struct smtpd *env, struct aliaseslist *aliases, char *username)
+{
+ int ret;
+ DBT key;
+ DBT val;
+ DB *aliasesdb;
+ size_t nbaliases;
+ struct alias alias;
+ struct alias *aliasp;
+ struct alias *nextalias;
+ struct map *map;
+
+ map = map_findbyname(env, "aliases");
+ if (map == NULL)
+ return 0;
+
+ if (map->m_src != S_DB) {
+ log_info("map source for \"aliases\" must be \"db\".");
+ return 0;
+ }
+
+ aliasesdb = dbopen(map->m_config, O_RDONLY, 0600, DB_HASH, NULL);
+ if (aliasesdb == NULL)
+ return 0;
+
+ key.data = username;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) != 0) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ nbaliases = val.size / sizeof(struct alias);
+ if (nbaliases == 0) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ nextalias = (struct alias *)val.data;
+ do {
+ alias = *nextalias;
+ ++nextalias;
+ if (alias.type == ALIAS_INCLUDE) {
+ aliases_expand_include(aliases, alias.u.filename);
+ }
+ else {
+ aliasp = calloc(1, sizeof(struct alias));
+ if (aliasp == NULL)
+ err(1, "calloc");
+ *aliasp = alias;
+ TAILQ_INSERT_HEAD(aliases, aliasp, entry);
+ }
+ } while (--nbaliases);
+ aliasesdb->close(aliasesdb);
+ return 1;
+}
+
+int
+aliases_virtual_exist(struct smtpd *env, struct path *path)
+{
+ int ret;
+ DBT key;
+ DBT val;
+ DB *aliasesdb;
+ struct map *map;
+ char strkey[STRLEN];
+
+ map = map_findbyname(env, "virtual");
+ if (map == NULL)
+ return 0;
+
+ if (map->m_src != S_DB) {
+ log_info("map source for \"aliases\" must be \"db\".");
+ return 0;
+ }
+
+ aliasesdb = dbopen(map->m_config, O_RDONLY, 0600, DB_HASH, NULL);
+ if (aliasesdb == NULL)
+ return 0;
+
+ if (snprintf(strkey, STRLEN, "%s@%s", path->user, path->domain)
+ >= STRLEN) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ key.data = strkey;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) != 0) {
+
+ if (snprintf(strkey, STRLEN, "@%s", path->domain)
+ >= STRLEN) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ key.data = strkey;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) != 0) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+ }
+ aliasesdb->close(aliasesdb);
+
+ return ret == 0 ? 1 : 0;
+}
+
+int
+aliases_virtual_get(struct smtpd *env, struct aliaseslist *aliases,
+ struct path *path)
+{
+ int ret;
+ DBT key;
+ DBT val;
+ DB *aliasesdb;
+ size_t nbaliases;
+ struct alias alias;
+ struct alias *aliasp;
+ struct alias *nextalias;
+ struct map *map;
+ char strkey[STRLEN];
+
+ map = map_findbyname(env, "virtual");
+ if (map == NULL)
+ return 0;
+
+ if (map->m_src != S_DB) {
+ log_info("map source for \"virtual\" must be \"db\".");
+ return 0;
+ }
+
+ aliasesdb = dbopen(map->m_config, O_RDONLY, 0600, DB_HASH, NULL);
+ if (aliasesdb == NULL)
+ return 0;
+
+ if (snprintf(strkey, STRLEN, "%s@%s", path->user, path->domain)
+ >= STRLEN) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ key.data = strkey;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) != 0) {
+
+ if (snprintf(strkey, STRLEN, "@%s", path->domain)
+ >= STRLEN) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ key.data = strkey;
+ key.size = strlen(key.data) + 1;
+
+ if ((ret = aliasesdb->get(aliasesdb, &key, &val, 0)) != 0) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+ }
+
+ nbaliases = val.size / sizeof(struct alias);
+ if (nbaliases == 0) {
+ aliasesdb->close(aliasesdb);
+ return 0;
+ }
+
+ nextalias = (struct alias *)val.data;
+ do {
+ alias = *nextalias;
+ ++nextalias;
+ if (alias.type == ALIAS_INCLUDE) {
+ aliases_expand_include(aliases, alias.u.filename);
+ }
+ else {
+ aliasp = calloc(1, sizeof(struct alias));
+ if (aliasp == NULL)
+ err(1, "calloc");
+ *aliasp = alias;
+ TAILQ_INSERT_HEAD(aliases, aliasp, entry);
+ }
+ } while (--nbaliases);
+ aliasesdb->close(aliasesdb);
+ return 1;
+}
+
+int
+aliases_expand_include(struct aliaseslist *aliases, char *filename)
+{
+ FILE *fp;
+ char *line;
+ size_t len;
+ size_t lineno = 0;
+ char delim[] = { '\\', '#' };
+ struct alias alias;
+ struct alias *aliasp;
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ warnx("failed to open include file \"%s\".", filename);
+ return 0;
+ }
+
+ while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) {
+ if (len == 0) {
+ free(line);
+ continue;
+ }
+ if (! alias_parse(&alias, line)) {
+ warnx("could not parse include entry \"%s\".", line);
+ }
+
+ if (alias.type == ALIAS_INCLUDE) {
+ warnx("nested inclusion is not supported.");
+ }
+ else {
+ aliasp = calloc(1, sizeof(struct alias));
+ if (aliasp == NULL)
+ err(1, "calloc");
+ *aliasp = alias;
+ TAILQ_INSERT_TAIL(aliases, aliasp, entry);
+ }
+
+ free(line);
+ }
+
+ fclose(fp);
+ return 1;
+}
+
+int
+alias_parse(struct alias *alias, char *line)
+{
+ size_t i;
+ int (*f[])(struct alias *, char *, size_t) = {
+ alias_is_include,
+ alias_is_filter,
+ alias_is_filename,
+ alias_is_address,
+ alias_is_username
+ };
+
+ for (i = 0; i < sizeof(f) / sizeof(void *); ++i) {
+ bzero(alias, sizeof(struct alias));
+ if (f[i](alias, line, strlen(line)))
+ break;
+ }
+ if (i == sizeof(f) / sizeof(void *))
+ return 0;
+
+ return 1;
+}
+
+
+int
+alias_is_filter(struct alias *alias, char *line, size_t len)
+{
+ if (strncmp(line, "\"|", 2) == 0 &&
+ line[len - 1] == '"') {
+ if (strlcpy(alias->u.filter, line, MAXPATHLEN) >=
+ MAXPATHLEN)
+ return 0;
+ alias->type = ALIAS_FILTER;
+ return 1;
+ }
+ return 0;
+}
+
+int
+alias_is_username(struct alias *alias, char *line, size_t len)
+{
+ if (len >= MAXLOGNAME)
+ return 0;
+
+ if (strlcpy(alias->u.username, line, MAXLOGNAME) >= MAXLOGNAME)
+ return 0;
+
+ while (*line) {
+ if (!isalnum(*line) &&
+ *line != '_' && *line != '.' && *line != '-')
+ return 0;
+ ++line;
+ }
+
+ alias->type = ALIAS_USERNAME;
+ return 1;
+}
+
+int
+alias_is_address(struct alias *alias, char *line, size_t len)
+{
+ char *domain;
+
+ if (len < 3) /* x@y */
+ return 0;
+
+ domain = strchr(line, '@');
+ if (domain == NULL)
+ return 0;
+
+ /* @ cannot start or end an address */
+ if (domain == line || domain == line + len)
+ return 0;
+
+ /* scan pre @ for disallowed chars */
+ *domain++ = '\0';
+ strlcpy(alias->u.path.user, line, MAXPATHLEN);
+ strlcpy(alias->u.path.domain, domain, MAXPATHLEN);
+
+ while (*line) {
+ char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
+ if (!isalnum(*line) &&
+ strchr(allowedset, *line) == NULL)
+ return 0;
+ ++line;
+ }
+
+ while (*domain) {
+ char allowedset[] = "-.";
+ if (!isalnum(*domain) &&
+ strchr(allowedset, *domain) == NULL)
+ return 0;
+ ++domain;
+ }
+
+ alias->type = ALIAS_ADDRESS;
+ return 1;
+}
+
+int
+alias_is_filename(struct alias *alias, char *line, size_t len)
+{
+ if (len >= MAXPATHLEN)
+ return 0;
+
+ if (*line != '/')
+ return 0;
+
+ strlcpy(alias->u.filename, line, MAXPATHLEN);
+ alias->type = ALIAS_FILENAME;
+ return 1;
+}
+
+int
+alias_is_include(struct alias *alias, char *line, size_t len)
+{
+ if (strncasecmp(":include:", line, 9) != 0)
+ return 0;
+
+ if (! alias_is_filename(alias, line + 9, len - 9))
+ return 0;
+
+ alias->type = ALIAS_INCLUDE;
+ return 1;
+}
diff --git a/usr.sbin/smtpd/atomic.c b/usr.sbin/smtpd/atomic.c
new file mode 100644
index 00000000000..ce245fedaad
--- /dev/null
+++ b/usr.sbin/smtpd/atomic.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2008 Charles Longeau <chl@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+ssize_t
+atomic_write(int d, const void *buf, size_t nbytes)
+{
+ ssize_t ret;
+ size_t n = nbytes;
+
+ do {
+ ret = write(d, buf, n);
+ if (ret == -1 && errno != EINTR)
+ return -1;
+ if (ret != -1)
+ n -= ret;
+ } while (n > 0 || (ret == -1 && errno == EINTR));
+
+ return nbytes;
+}
+
+ssize_t
+atomic_read(int d, void *buf, size_t nbytes)
+{
+ ssize_t ret;
+ size_t n = nbytes;
+
+ do {
+ ret = read(d, buf, n);
+ if (ret == -1 && errno != EINTR)
+ return -1;
+ if (ret != -1)
+ n -= ret;
+
+ if (ret == 0 && n != 0)
+ return -1;
+
+ } while (n > 0 || (ret == -1 && errno == EINTR));
+
+ return nbytes;
+}
+
+ssize_t
+atomic_printfd(int d, const char *fmt, ...)
+{
+ int ret;
+ char *buf;
+
+ va_list ap;
+ va_start(ap, fmt);
+
+ if ((ret = vasprintf(&buf, fmt, ap)) == -1) {
+ va_end(ap);
+ return -1;
+ }
+ va_end(ap);
+
+ ret = atomic_write(d, buf, ret);
+ free(buf);
+
+ return ret;
+}
diff --git a/usr.sbin/smtpd/buffer.c b/usr.sbin/smtpd/buffer.c
new file mode 100644
index 00000000000..81da8e505f5
--- /dev/null
+++ b/usr.sbin/smtpd/buffer.c
@@ -0,0 +1,247 @@
+/* $OpenBSD: buffer.c,v 1.1 2008/11/01 21:35:28 gilles Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+int buf_realloc(struct buf *, size_t);
+void buf_enqueue(struct msgbuf *, struct buf *);
+void buf_dequeue(struct msgbuf *, struct buf *);
+
+struct buf *
+buf_open(size_t len)
+{
+ struct buf *buf;
+
+ if ((buf = calloc(1, sizeof(struct buf))) == NULL)
+ return (NULL);
+ if ((buf->buf = malloc(len)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf->size = buf->max = len;
+ buf->fd = -1;
+
+ return (buf);
+}
+
+struct buf *
+buf_dynamic(size_t len, size_t max)
+{
+ struct buf *buf;
+
+ if (max < len)
+ return (NULL);
+
+ if ((buf = buf_open(len)) == NULL)
+ return (NULL);
+
+ if (max > 0)
+ buf->max = max;
+
+ return (buf);
+}
+
+int
+buf_realloc(struct buf *buf, size_t len)
+{
+ u_char *b;
+
+ /* on static buffers max is eq size and so the following fails */
+ if (buf->wpos + len > buf->max) {
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ b = realloc(buf->buf, buf->wpos + len);
+ if (b == NULL)
+ return (-1);
+ buf->buf = b;
+ buf->size = buf->wpos + len;
+
+ return (0);
+}
+
+int
+buf_add(struct buf *buf, void *data, size_t len)
+{
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (-1);
+
+ memcpy(buf->buf + buf->wpos, data, len);
+ buf->wpos += len;
+ return (0);
+}
+
+void *
+buf_reserve(struct buf *buf, size_t len)
+{
+ void *b;
+
+ if (buf->wpos + len > buf->size)
+ if (buf_realloc(buf, len) == -1)
+ return (NULL);
+
+ b = buf->buf + buf->wpos;
+ buf->wpos += len;
+ return (b);
+}
+
+int
+buf_close(struct msgbuf *msgbuf, struct buf *buf)
+{
+ buf_enqueue(msgbuf, buf);
+ return (1);
+}
+
+void
+buf_free(struct buf *buf)
+{
+ free(buf->buf);
+ free(buf);
+}
+
+void
+msgbuf_init(struct msgbuf *msgbuf)
+{
+ msgbuf->queued = 0;
+ msgbuf->fd = -1;
+ TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+ struct buf *buf;
+
+ while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+ buf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+ struct iovec iov[IOV_MAX];
+ struct buf *buf, *next, *save = NULL;
+ int i = 0;
+ ssize_t n;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int))];
+ } cmsgbuf;
+
+ bzero(&iov, sizeof(iov));
+ bzero(&msg, sizeof(msg));
+ TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+ if (i >= IOV_MAX)
+ break;
+ if (buf->fd != -1 && i != 0) {
+ buf = save;
+ break;
+ }
+ iov[i].iov_base = buf->buf + buf->rpos;
+ iov[i].iov_len = buf->size - buf->rpos;
+ i++;
+ if (buf->fd != -1)
+ break;
+ save = buf;
+ }
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ if (buf != NULL && buf->fd != -1) {
+ msg.msg_control = (caddr_t)&cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ *(int *)CMSG_DATA(cmsg) = buf->fd;
+ }
+
+ if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+ if (errno == EAGAIN || errno == ENOBUFS ||
+ errno == EINTR) /* try later */
+ return (0);
+ else
+ return (-1);
+ }
+
+ if (n == 0) { /* connection closed */
+ errno = 0;
+ return (-2);
+ }
+
+ if (buf != NULL && buf->fd != -1) {
+ close(buf->fd);
+ buf->fd = -1;
+ }
+
+ for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+ buf = next) {
+ next = TAILQ_NEXT(buf, entry);
+ if (buf->rpos + n >= buf->size) {
+ n -= buf->size - buf->rpos;
+ buf_dequeue(msgbuf, buf);
+ } else {
+ buf->rpos += n;
+ n = 0;
+ }
+ }
+
+ return (0);
+}
+
+void
+buf_enqueue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+ msgbuf->queued++;
+}
+
+void
+buf_dequeue(struct msgbuf *msgbuf, struct buf *buf)
+{
+ TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+
+ if (buf->fd != -1)
+ close(buf->fd);
+
+ msgbuf->queued--;
+ buf_free(buf);
+}
diff --git a/usr.sbin/smtpd/config.c b/usr.sbin/smtpd/config.c
new file mode 100644
index 00000000000..49e5ebbd0a6
--- /dev/null
+++ b/usr.sbin/smtpd/config.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <event.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+int is_peer(struct peer *, enum smtp_proc_type, u_int);
+
+int
+is_peer(struct peer *p, enum smtp_proc_type peer, u_int peercount)
+{
+ u_int i;
+
+ for (i = 0; i < peercount; i++)
+ if (p[i].id == peer)
+ return (1);
+ return (0);
+}
+
+void
+unconfigure(struct smtpd *env)
+{
+}
+
+void
+configure(struct smtpd *env)
+{
+}
+
+void
+purge_config(struct smtpd *env, u_int8_t what)
+{
+ struct listener *l;
+ struct map *m;
+ struct rule *r;
+ struct cond *c;
+ struct opt *o;
+ struct ssl *s;
+
+ if (what & PURGE_LISTENERS) {
+ while ((l = TAILQ_FIRST(&env->sc_listeners)) != NULL) {
+ TAILQ_REMOVE(&env->sc_listeners, l, entry);
+ free(l);
+ }
+ TAILQ_INIT(&env->sc_listeners);
+ }
+ if (what & PURGE_MAPS) {
+ while ((m = TAILQ_FIRST(env->sc_maps)) != NULL) {
+ TAILQ_REMOVE(env->sc_maps, m, m_entry);
+ free(m);
+ }
+ free(env->sc_maps);
+ env->sc_maps = NULL;
+ }
+ if (what & PURGE_RULES) {
+ while ((r = TAILQ_FIRST(env->sc_rules)) != NULL) {
+ TAILQ_REMOVE(env->sc_rules, r, r_entry);
+ while ((c = TAILQ_FIRST(&r->r_conditions)) != NULL) {
+ TAILQ_REMOVE(&r->r_conditions, c, c_entry);
+ free(c);
+ }
+ while ((o = TAILQ_FIRST(&r->r_options)) != NULL) {
+ TAILQ_REMOVE(&r->r_options, o, o_entry);
+ free(o);
+ }
+ free(r);
+ }
+ env->sc_rules = NULL;
+ }
+ if (what & PURGE_SSL) {
+ while ((s = SPLAY_ROOT(&env->sc_ssl)) != NULL) {
+ SPLAY_REMOVE(ssltree, &env->sc_ssl, s);
+ free(s->ssl_cert);
+ free(s->ssl_key);
+ free(s);
+ }
+ SPLAY_INIT(&env->sc_ssl);
+ }
+}
+
+void
+init_peers(struct smtpd *env)
+{
+ int i;
+ int j;
+
+ for (i = 0; i < PROC_COUNT; i++)
+ for (j = 0; j < PROC_COUNT; j++) {
+ if (i >= j)
+ continue;
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC,
+ env->sc_pipes[i][j]) == -1)
+ fatal("socketpair");
+ session_socket_blockmode(env->sc_pipes[i][j][0],
+ BM_NONBLOCK);
+ session_socket_blockmode(env->sc_pipes[i][j][1],
+ BM_NONBLOCK);
+ }
+}
+
+void
+config_peers(struct smtpd *env, struct peer *p, u_int peercount)
+{
+ u_int i;
+ u_int j;
+ u_int src;
+ u_int dst;
+ u_int idx;
+
+ /*
+ * close pipes
+ */
+ for (i = 0; i < PROC_COUNT; i++) {
+ for (j = 0; j < PROC_COUNT; j++) {
+ if (i >= j)
+ continue;
+
+ if ((i == smtpd_process && is_peer(p, j, peercount)) ||
+ (j == smtpd_process && is_peer(p, i, peercount))) {
+ idx = (i == smtpd_process)?1:0;
+ close(env->sc_pipes[i][j][idx]);
+ } else {
+ close(env->sc_pipes[i][j][0]);
+ close(env->sc_pipes[i][j][1]);
+ }
+ }
+ }
+
+ /*
+ * listen on appropriate pipes
+ */
+ for (i = 0; i < peercount; i++) {
+
+ if (p[i].id == smtpd_process)
+ fatal("config_peers: cannot peer with oneself");
+
+ src = (smtpd_process < p[i].id)?smtpd_process:p[i].id;
+ dst = (src == p[i].id)?smtpd_process:p[i].id;
+
+ if ((env->sc_ibufs[p[i].id] =
+ calloc(1, sizeof(struct imsgbuf))) == NULL)
+ fatal("config_peers");
+
+ idx = (src == smtpd_process)?0:1;
+ imsg_init(env->sc_ibufs[p[i].id],
+ env->sc_pipes[src][dst][idx], p[i].cb);
+ env->sc_ibufs[p[i].id]->events = EV_READ;
+ env->sc_ibufs[p[i].id]->data = env;
+ event_set(&env->sc_ibufs[p[i].id]->ev,
+ env->sc_ibufs[p[i].id]->fd,
+ env->sc_ibufs[p[i].id]->events,
+ env->sc_ibufs[p[i].id]->handler,
+ env->sc_ibufs[p[i].id]->data);
+ event_add(&env->sc_ibufs[p[i].id]->ev, NULL);
+ }
+}
diff --git a/usr.sbin/smtpd/control.c b/usr.sbin/smtpd/control.c
new file mode 100644
index 00000000000..6a34df67fdc
--- /dev/null
+++ b/usr.sbin/smtpd/control.c
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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/tree.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+#define CONTROL_BACKLOG 5
+
+/* control specific headers */
+struct {
+ struct event ev;
+ int fd;
+} control_state;
+
+__dead void control_shutdown(void);
+int control_init(void);
+int control_listen(struct smtpd *);
+void control_cleanup(void);
+void control_accept(int, short, void *);
+struct ctl_conn *control_connbyfd(int);
+void control_close(int);
+void control_sig_handler(int, short, void *);
+void control_dispatch_ext(int, short, void *);
+void control_dispatch_lka(int, short, void *);
+void control_dispatch_mfa(int, short, void *);
+void control_dispatch_queue(int, short, void *);
+
+struct ctl_connlist ctl_conns;
+
+void
+control_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ control_shutdown();
+ break;
+ default:
+ fatalx("control_sig_handler: unexpected signal");
+ }
+}
+
+
+pid_t
+control(struct smtpd *env)
+{
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask;
+ pid_t pid;
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct peer peers [] = {
+ { PROC_QUEUE, control_dispatch_queue },
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("control: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ fatal("control: socket");
+
+ sun.sun_family = AF_UNIX;
+ if (strlcpy(sun.sun_path, SMTPD_SOCKET,
+ sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
+ fatal("control: socket name too long");
+
+ if (unlink(SMTPD_SOCKET) == -1)
+ if (errno != ENOENT)
+ fatal("control: cannot unlink socket");
+
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ (void)umask(old_umask);
+ fatal("control: bind");
+ }
+ (void)umask(old_umask);
+
+ if (chmod(SMTPD_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+ (void)unlink(SMTPD_SOCKET);
+ fatal("control: chmod");
+ }
+
+ session_socket_blockmode(fd, BM_NONBLOCK);
+ control_state.fd = fd;
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("control: chroot");
+ if (chdir("/") == -1)
+ fatal("control: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("control process");
+ smtpd_process = PROC_CONTROL;
+
+#ifndef DEBUG
+ 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("control: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, control_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, control_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ TAILQ_INIT(&ctl_conns);
+
+ config_peers(env, peers, 1);
+ control_listen(env);
+ event_dispatch();
+ control_shutdown();
+
+ return (0);
+}
+
+void
+control_shutdown(void)
+{
+ log_info("control process exiting");
+ _exit(0);
+}
+
+int
+control_listen(struct smtpd *env)
+{
+ if (listen(control_state.fd, CONTROL_BACKLOG) == -1) {
+ log_warn("control_listen: listen");
+ return (-1);
+ }
+
+ event_set(&control_state.ev, control_state.fd, EV_READ | EV_PERSIST,
+ control_accept, env);
+ event_add(&control_state.ev, NULL);
+
+ return (0);
+}
+
+void
+control_cleanup(void)
+{
+ (void)unlink(SMTPD_SOCKET);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+ struct smtpd *env = arg;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ if (errno != EWOULDBLOCK && errno != EINTR)
+ log_warn("control_accept");
+ return;
+ }
+
+ session_socket_blockmode(connfd, BM_NONBLOCK);
+
+ if ((c = malloc(sizeof(struct ctl_conn))) == NULL) {
+ close(connfd);
+ log_warn("control_accept");
+ return;
+ }
+
+ imsg_init(&c->ibuf, connfd, control_dispatch_ext);
+ c->ibuf.events = EV_READ;
+ event_set(&c->ibuf.ev, c->ibuf.fd, c->ibuf.events,
+ c->ibuf.handler, env);
+ event_add(&c->ibuf.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL)
+ log_warn("control_close: fd %d: not found", fd);
+
+ msgbuf_clear(&c->ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->ibuf.ev);
+ close(c->ibuf.fd);
+ free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_ext(int fd, short event, void *arg)
+{
+ struct ctl_conn *c;
+ struct smtpd *env = arg;
+ struct imsg imsg;
+ int n;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("control_dispatch_ext: fd %d: not found", fd);
+ return;
+ }
+
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(&c->ibuf)) == -1 || n == 0) {
+ control_close(fd);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&c->ibuf.w) < 0) {
+ control_close(fd);
+ return;
+ }
+ imsg_event_add(&c->ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->ibuf, &imsg)) == -1) {
+ control_close(fd);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_SHUTDOWN:
+ /* NEEDS_FIX */
+ log_debug("received shutdown request");
+ if (env->sc_flags & SMTPD_EXITING) {
+ imsg_compose(&c->ibuf, IMSG_CTL_FAIL, 0, 0, -1,
+ NULL, 0);
+ break;
+ }
+ env->sc_flags |= SMTPD_EXITING;
+ imsg_compose(&c->ibuf, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
+ break;
+ default:
+ log_debug("control_dispatch_ext: "
+ "error handling imsg %d", imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->ibuf);
+}
+
+void
+control_dispatch_lka(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_LKA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("control_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("control_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+control_dispatch_mfa(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MFA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("control_dispatch_mfa: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("control_dispatch_mfa: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+control_dispatch_queue(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_QUEUE];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("control_dispatch_queue: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("control_dispatch_queue: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+session_socket_blockmode(int fd, enum blockmodes bm)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fcntl F_GETFL");
+
+ if (bm == BM_NONBLOCK)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ fatal("fcntl F_SETFL");
+}
diff --git a/usr.sbin/smtpd/debug.c b/usr.sbin/smtpd/debug.c
new file mode 100644
index 00000000000..73f8b0f64e2
--- /dev/null
+++ b/usr.sbin/smtpd/debug.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+void debug_display_batch(struct batch *);
+void debug_display_message(struct message *);
+
+void
+debug_display_batch(struct batch *p)
+{
+ log_debug("batch # : %qd", p->id);
+
+ if (p->type & T_MDA_BATCH) {
+ log_debug("type : MDA");
+ }
+ else if (p->type & T_MTA_BATCH) {
+ log_debug("type : MTA");
+ }
+
+ if (p->type & T_DAEMON_BATCH) {
+ log_debug("mailer-daemon : yes");
+ }
+ else {
+ log_debug("mailer-daemon : no");
+ }
+
+ log_debug("creation date : %lu", p->creation);
+ log_debug("flags : %s%s%s%s",
+ (p->flags & F_BATCH_COMPLETE) ? " [COMPLETE] " : "",
+ (p->flags & F_BATCH_RESOLVED) ? " [RESOLVED] " : "",
+ (p->flags & F_BATCH_SCHEDULED) ? " [SCHEDULED] " : "",
+ (p->flags == 0) ? " [NONE] " : "");
+}
+
+void
+debug_display_message(struct message *p)
+{
+ log_debug("message # : %qd", p->id);
+ log_debug("session # : %qd", p->session_id);
+ log_debug("batch # : %qd", p->batch_id);
+ log_debug("message-id : %s", p->message_id);
+ log_debug("message-uid : %s", p->message_uid);
+ log_debug("sender : %s@%s", p->sender.user,
+ p->sender.domain);
+ log_debug("recipient : %s@%s", p->recipient.user,
+ p->recipient.domain);
+
+ if (p->type & T_MDA_MESSAGE) {
+ log_debug("type : MDA");
+ }
+ else if (p->type & T_MTA_MESSAGE) {
+ log_debug("type : MTA");
+ }
+
+ if (p->type & T_DAEMON_MESSAGE) {
+ log_debug("mailer-daemon : yes");
+ }
+ else {
+ log_debug("mailer-daemon : no");
+ }
+
+ log_debug("creation date : %lu", p->creation);
+ log_debug("last attempt : %lu", p->lasttry);
+ log_debug("retry count : %lu", p->retry);
+ log_debug("flags : %lu", p->flags);
+ log_debug("status : %s%s%s%s%s%s%s%s",
+ (p->status & S_MESSAGE_PERMFAILURE) ? " [PERMFAILURE]" : "",
+ (p->status & S_MESSAGE_TEMPFAILURE) ? " [TEMPFAILURE]" : "",
+ (p->status & S_MESSAGE_REJECTED) ? " [REJECTED]" : "",
+ (p->status & S_MESSAGE_ACCEPTED) ? " [ACCEPTED]" : "",
+ (p->status & S_MESSAGE_RETRY) ? " [RETRY]" : "",
+ (p->status & S_MESSAGE_EDNS) ? " [DNS]" : "",
+ (p->status & S_MESSAGE_ECONNECT) ? " [CONNECT]" : "",
+ (p->status == 0) ? "[NONE]" : "");
+
+}
diff --git a/usr.sbin/smtpd/dns.c b/usr.sbin/smtpd/dns.c
new file mode 100644
index 00000000000..08cabed9813
--- /dev/null
+++ b/usr.sbin/smtpd/dns.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/queue.h>
+#include <sys/tree.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+
+#include <err.h>
+#include <event.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+struct mxrecord {
+ char hostname[MAXHOSTNAMELEN];
+ u_int16_t priority;
+};
+
+static void mxsort(struct mxrecord *, size_t);
+size_t getmxbyname(char *, char ***);
+
+
+/* bubble sort MX records by priority */
+static void
+mxsort(struct mxrecord *array, size_t len)
+{
+ u_int32_t i;
+ u_int32_t j;
+ struct mxrecord store;
+
+ for (i = j = 0; i < len - 1; ++i) {
+ for (j = i + 1; j < len; ++j) {
+ if (array[i].priority > array[j].priority) {
+ store = array[i];
+ array[i] = array[j];
+ array[j] = store;
+ }
+ }
+ }
+}
+
+size_t
+getmxbyname(char *name, char ***result)
+{
+ union {
+ u_int8_t bytes[PACKETSZ];
+ HEADER header;
+ } answer;
+ u_int32_t i, j;
+ int ret;
+ u_int8_t *sp;
+ u_int8_t *endp;
+ u_int8_t *ptr;
+ u_int16_t qdcount;
+ u_int8_t expbuf[PACKETSZ];
+ u_int16_t type;
+ u_int16_t n;
+ u_int16_t priority, tprio;
+ size_t mxnb;
+ struct mxrecord mxarray[MXARRAYSIZE];
+ size_t chunklen;
+
+ ret = res_query(name, C_IN, T_MX, (u_int8_t *)&answer.bytes,
+ sizeof answer);
+ if (ret == -1)
+ return 0;
+
+ /* sp stores start of dns packet,
+ * endp stores end of dns packet,
+ */
+ sp = (u_int8_t *)&answer.bytes;
+ endp = sp + ret;
+
+ /* skip header */
+ ptr = sp + HFIXEDSZ;
+
+ for (qdcount = ntohs(answer.header.qdcount);
+ qdcount--;
+ ptr += ret + QFIXEDSZ) {
+ ret = dn_skipname(ptr, endp);
+ if (ret == -1)
+ return 0;
+ }
+
+ mxnb = 0;
+ for (; ptr < endp;) {
+ memset(expbuf, 0, sizeof expbuf);
+ ret = dn_expand(sp, endp, ptr, expbuf, sizeof expbuf);
+ if (ret == -1)
+ break;
+ ptr += ret;
+
+ GETSHORT(type, ptr);
+ ptr += sizeof(u_int16_t) + sizeof(u_int32_t);
+ GETSHORT(n, ptr);
+
+ if (type != T_MX) {
+ ptr += n;
+ continue;
+ }
+
+ GETSHORT(priority, ptr);
+ ret = dn_expand(sp, endp, ptr, expbuf, sizeof expbuf);
+ if (ret == -1)
+ return 0;
+ ptr += ret;
+
+ if (mxnb < sizeof(mxarray) / sizeof(struct mxrecord)) {
+ if (strlcpy(mxarray[mxnb].hostname, expbuf,
+ MAXHOSTNAMELEN) >= MAXHOSTNAMELEN)
+ return 0;
+ mxarray[mxnb].priority = priority;
+ if (tprio < priority)
+ tprio = priority;
+ }
+ else {
+ for (i = j = 0;
+ i < sizeof(mxarray) / sizeof(struct mxrecord);
+ ++i) {
+ if (tprio < mxarray[i].priority) {
+ tprio = mxarray[i].priority;
+ j = i;
+ }
+ }
+
+ if (mxarray[j].priority > priority) {
+ if (strlcpy(mxarray[j].hostname, expbuf,
+ MAXHOSTNAMELEN) >= MAXHOSTNAMELEN)
+ return 0;
+ mxarray[j].priority = priority;
+ }
+ }
+ ++mxnb;
+ }
+
+ if (mxnb == 0)
+ return 0;
+
+ if (mxnb > sizeof(mxarray) / sizeof(struct mxrecord))
+ mxnb = sizeof(mxarray) / sizeof(struct mxrecord);
+
+ /* Rearrange MX records by priority */
+ mxsort((struct mxrecord *)&mxarray, mxnb);
+
+ chunklen = 0;
+ for (i = 0; i < mxnb; ++i)
+ chunklen += strlen(mxarray[i].hostname) + 1;
+ chunklen += ((mxnb + 1) * sizeof(char *));
+
+ *result = calloc(1, chunklen);
+ if (*result == NULL) {
+ err(1, "calloc");
+ }
+
+ ptr = (u_int8_t *)*result + (mxnb + 1) * sizeof(char *);
+ for (i = 0; i < mxnb; ++i) {
+ strlcpy(ptr, mxarray[i].hostname, MAXHOSTNAMELEN);
+ (*result)[i] = ptr;
+ ptr += strlen(mxarray[i].hostname) + 1;
+ }
+ (*result)[i] = NULL;
+
+ return mxnb;
+}
diff --git a/usr.sbin/smtpd/forward.c b/usr.sbin/smtpd/forward.c
new file mode 100644
index 00000000000..a1d510f9b4b
--- /dev/null
+++ b/usr.sbin/smtpd/forward.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "smtpd.h"
+
+int alias_parse(struct alias *, char *);
+int forwards_get(struct aliaseslist *, char *);
+
+int
+forwards_get(struct aliaseslist *aliases, char *username)
+{
+ FILE *fp;
+ struct alias alias;
+ struct alias *aliasp;
+ char pathname[MAXPATHLEN];
+ struct passwd *pw;
+ char *buf, *lbuf;
+ size_t len;
+ struct stat sb;
+
+ pw = getpwnam(username);
+ if (pw == NULL)
+ return 0;
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/.forward", pw->pw_dir)
+ >= MAXPATHLEN)
+ return 0;
+
+ fp = fopen(pathname, "r");
+ if (fp == NULL)
+ return 0;
+
+ /* make sure ~/ is not writable by anyone but owner */
+ if (stat(pw->pw_dir, &sb) == -1)
+ goto bad;
+ if (sb.st_uid != pw->pw_uid || sb.st_mode & (S_IWGRP|S_IWOTH))
+ goto bad;
+
+ /* make sure ~/.forward is not writable by anyone but owner */
+ if (fstat(fileno(fp), &sb) == -1)
+ goto bad;
+ if (sb.st_uid != pw->pw_uid || sb.st_mode & (S_IWGRP|S_IWOTH))
+ goto bad;
+
+ lbuf = NULL;
+ while ((buf = fgetln(fp, &len))) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else {
+ /* EOF without EOL, copy and add the NUL */
+ if ((lbuf = malloc(len + 1)) == NULL)
+ err(1, NULL);
+ memcpy(lbuf, buf, len);
+ lbuf[len] = '\0';
+ buf = lbuf;
+ }
+ printf("%s\n", buf);
+ if (! alias_parse(&alias, buf)) {
+ log_debug("bad entry in ~/.forward");
+ continue;
+ }
+
+ if (alias.type == ALIAS_INCLUDE) {
+ log_debug("includes are forbidden in ~/.forward");
+ continue;
+ }
+ aliasp = calloc(1, sizeof(struct alias));
+ if (aliasp == NULL)
+ err(1, "calloc");
+ *aliasp = alias;
+ TAILQ_INSERT_HEAD(aliases, aliasp, entry);
+
+ }
+ free(lbuf);
+ fclose(fp);
+ return 1;
+
+bad:
+ if (fp != NULL)
+ fclose(fp);
+ return 0;
+}
diff --git a/usr.sbin/smtpd/imsg.c b/usr.sbin/smtpd/imsg.c
new file mode 100644
index 00000000000..00665191dca
--- /dev/null
+++ b/usr.sbin/smtpd/imsg.c
@@ -0,0 +1,317 @@
+/* $OpenBSD: imsg.c,v 1.1 2008/11/01 21:35:28 gilles Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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/stat.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <event.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+static u_int32_t imsgid = 1;
+
+void
+imsg_init(struct imsgbuf *ibuf, int fd, void (*handler)(int, short, void *))
+{
+ if (!ibuf->pid) {
+ msgbuf_init(&ibuf->w);
+ bzero(&ibuf->r, sizeof(ibuf->r));
+ ibuf->pid = getpid();
+ ibuf->handler = handler;
+ TAILQ_INIT(&ibuf->fds);
+ }
+ ibuf->fd = fd;
+ ibuf->w.fd = fd;
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr hdr;
+ char buf[CMSG_SPACE(sizeof(int) * 16)];
+ } cmsgbuf;
+ struct iovec iov;
+ ssize_t n;
+ int fd;
+ struct imsg_fd *ifd;
+
+ bzero(&msg, sizeof(msg));
+
+ iov.iov_base = ibuf->r.buf + ibuf->r.wpos;
+ iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsgbuf.buf;
+ msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+ if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) {
+ if (errno != EINTR && errno != EAGAIN && errno != EMSGSIZE) {
+ log_warn("imsg_read: pipe read error");
+ return (-1);
+ }
+ return (-2);
+ }
+ ibuf->r.wpos += n;
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ fd = (*(int *)CMSG_DATA(cmsg));
+ if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL)
+ fatal("imsg_read calloc");
+ ibuf->id = imsgid++;
+ ifd->fd = fd;
+ ifd->id = ibuf->id;
+ TAILQ_INSERT_TAIL(&ibuf->fds, ifd, entry);
+ } else
+ log_warn("imsg_read: got unexpected ctl data level %d "
+ "type %d", cmsg->cmsg_level, cmsg->cmsg_type);
+ }
+
+ return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ size_t av, left, datalen;
+
+ av = ibuf->r.wpos;
+
+ if (IMSG_HEADER_SIZE > av)
+ return (0);
+
+ memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+ if (imsg->hdr.len < IMSG_HEADER_SIZE ||
+ imsg->hdr.len > MAX_IMSGSIZE) {
+ log_warnx("imsg_get: imsg hdr len %u out of bounds, type=%u",
+ imsg->hdr.len, imsg->hdr.type);
+ return (-1);
+ }
+ if (imsg->hdr.len > av)
+ return (0);
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+ if ((imsg->data = malloc(datalen)) == NULL) {
+ log_warn("imsg_get");
+ return (-1);
+ }
+
+ memcpy(imsg->data, ibuf->r.rptr, datalen);
+ imsg->id = 0;
+ if (ibuf->id != 0) {
+ imsg->id = ibuf->id;
+ ibuf->id = 0;
+ }
+
+ if (imsg->hdr.len < av) {
+ left = av - imsg->hdr.len;
+ memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+ ibuf->r.wpos = left;
+ } else
+ ibuf->r.wpos = 0;
+
+ return (datalen + IMSG_HEADER_SIZE);
+}
+
+int
+imsg_compose(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, int fd, void *data, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ int n;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ if (imsg_add(wbuf, data, datalen) == -1)
+ return (-1);
+
+ wbuf->fd = fd;
+
+ if ((n = imsg_close(ibuf, wbuf)) < 0)
+ return (-1);
+
+ return (n);
+}
+
+int
+imsg_composev(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+ struct buf *wbuf;
+ int n;
+ int i, datalen = 0;
+
+ for (i = 0; i < iovcnt; i++)
+ datalen += iov[i].iov_len;
+
+ if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+ return (-1);
+
+ for (i = 0; i < iovcnt; i++)
+ if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1)
+ return (-1);
+
+ wbuf->fd = fd;
+
+ if ((n = imsg_close(ibuf, wbuf)) < 0)
+ return (-1);
+
+ return (n);
+}
+
+/* ARGSUSED */
+struct buf *
+imsg_create(struct imsgbuf *ibuf, enum imsg_type type, u_int32_t peerid,
+ pid_t pid, u_int16_t datalen)
+{
+ struct buf *wbuf;
+ struct imsg_hdr hdr;
+
+ datalen += IMSG_HEADER_SIZE;
+ if (datalen > MAX_IMSGSIZE) {
+ log_warnx("imsg_create: len %u > MAX_IMSGSIZE; "
+ "type %u peerid %lu", datalen + IMSG_HEADER_SIZE,
+ type, peerid);
+ return (NULL);
+ }
+
+ hdr.type = type;
+ hdr.peerid = peerid;
+ if ((hdr.pid = pid) == 0)
+ hdr.pid = ibuf->pid;
+ if ((wbuf = buf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) {
+ log_warn("imsg_create: buf_open");
+ return (NULL);
+ }
+ if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+ return (NULL);
+
+ return (wbuf);
+}
+
+int
+imsg_add(struct buf *msg, void *data, u_int16_t datalen)
+{
+ if (datalen)
+ if (buf_add(msg, data, datalen) == -1) {
+ log_warnx("imsg_add: buf_add error");
+ buf_free(msg);
+ return (-1);
+ }
+ return (datalen);
+}
+
+int
+imsg_append(struct imsgbuf *ibuf, struct buf *msg)
+{
+ int n;
+ struct imsg_hdr *hdr;
+
+ hdr = (struct imsg_hdr *)msg->buf;
+ hdr->len = (u_int16_t)msg->wpos;
+ if ((n = buf_close(&ibuf->w, msg)) < 0) {
+ log_warnx("imsg_close: buf_close error");
+ buf_free(msg);
+ return (-1);
+ }
+
+ return (n);
+}
+
+int
+imsg_close(struct imsgbuf *ibuf, struct buf *msg)
+{
+ int n;
+ struct imsg_hdr *hdr;
+
+ hdr = (struct imsg_hdr *)msg->buf;
+ hdr->len = (u_int16_t)msg->wpos;
+ if ((n = buf_close(&ibuf->w, msg)) < 0) {
+ log_warnx("imsg_close: buf_close error");
+ buf_free(msg);
+ return (-1);
+ }
+ imsg_event_add(ibuf);
+
+ return (n);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+ free(imsg->data);
+}
+
+int
+imsg_get_fd(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+ int fd;
+ struct imsg_fd *ifd;
+
+ if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL)
+ return (-1);
+
+ TAILQ_FOREACH(ifd, &ibuf->fds, entry) {
+ if (ifd->id == imsg->id)
+ break;
+ }
+
+ if (ifd == NULL)
+ return (-1);
+
+ fd = ifd->fd;
+
+ TAILQ_REMOVE(&ibuf->fds, ifd, entry);
+ free(ifd);
+
+ return (fd);
+}
+
+int
+imsg_flush(struct imsgbuf *ibuf)
+{
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) < 0)
+ return (-1);
+ return (0);
+}
+
+void
+imsg_clear(struct imsgbuf *ibuf)
+{
+ while (ibuf->w.queued)
+ msgbuf_clear(&ibuf->w);
+}
diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c
new file mode 100644
index 00000000000..f33b4cf5eb1
--- /dev/null
+++ b/usr.sbin/smtpd/lka.c
@@ -0,0 +1,1013 @@
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <db.h>
+#include <err.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "smtpd.h"
+
+__dead void lka_shutdown(void);
+void lka_sig_handler(int, short, void *);
+void lka_dispatch_parent(int, short, void *);
+void lka_dispatch_mfa(int, short, void *);
+void lka_dispatch_smtp(int, short, void *);
+void lka_dispatch_queue(int, short, void *);
+void lka_setup_events(struct smtpd *);
+void lka_disable_events(struct smtpd *);
+int lka_verify_mail(struct smtpd *, struct path *);
+int lka_verify_rcpt(struct smtpd *, struct path *, struct sockaddr_storage *);
+int lka_resolve_mail(struct smtpd *, struct rule *, struct path *);
+int lka_resolve_rcpt(struct smtpd *, struct rule *, struct path *);
+int lka_forward_file(struct passwd *);
+size_t getmxbyname(char *, char ***);
+int lka_expand(char *, size_t, struct path *);
+int aliases_exist(struct smtpd *, char *);
+int aliases_get(struct smtpd *, struct aliaseslist *, char *);
+int lka_resolve_alias(struct smtpd *, struct imsgbuf *, struct message *, struct alias *);
+int lka_parse_include(char *);
+int forwards_get(struct aliaseslist *, char *);
+int lka_check_source(struct smtpd *, struct map *, struct sockaddr_storage *);
+int lka_match_mask(struct sockaddr_storage *, struct netaddr *);
+int aliases_virtual_get(struct smtpd *, struct aliaseslist *, struct path *);
+int aliases_virtual_exist(struct smtpd *, struct path *);
+
+void
+lka_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ lka_shutdown();
+ break;
+ default:
+ fatalx("lka_sig_handler: unexpected signal");
+ }
+}
+
+void
+lka_dispatch_parent(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_PARENT];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("parent_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+lka_dispatch_mfa(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MFA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("lka_dispatch_mfa: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_LKA_LOOKUP_MAIL: {
+ struct submit_status *ss;
+
+ ss = imsg.data;
+ ss->code = 530;
+
+ if (ss->u.path.user[0] == '\0' && ss->u.path.domain[0] == '\0')
+ ss->code = 250;
+ else
+ if (lka_verify_mail(env, &ss->u.path))
+ ss->code = 250;
+
+ imsg_compose(ibuf, IMSG_MFA_LOOKUP_MAIL, 0, 0, -1,
+ ss, sizeof(*ss));
+
+ break;
+ }
+ case IMSG_LKA_LOOKUP_RCPT: {
+ struct submit_status *ss;
+
+ ss = imsg.data;
+ ss->code = 530;
+
+ if (lka_verify_rcpt(env, &ss->u.path, &ss->ss))
+ ss->code = 250;
+
+ imsg_compose(ibuf, IMSG_MFA_LOOKUP_RCPT, 0, 0, -1,
+ ss, sizeof(*ss));
+
+ break;
+ }
+ default:
+ log_debug("lka_dispatch_mfa: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+lka_dispatch_smtp(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_SMTP];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("lka_dispatch_mfa: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_LKA_HOSTNAME_LOOKUP: {
+
+ struct sockaddr *sa = NULL;
+ socklen_t salen;
+ char addr[NI_MAXHOST];
+ struct addrinfo hints, *res;
+ int error;
+ struct session *s = imsg.data;
+
+ if (s->s_ss.ss_family == PF_INET) {
+ struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss;
+ sa = (struct sockaddr *)ssin;
+ }
+ if (s->s_ss.ss_family == PF_INET6) {
+ struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss;
+ sa = (struct sockaddr *)ssin6;
+ }
+
+ error = getnameinfo(sa, sa->sa_len, addr, sizeof(addr),
+ NULL, 0, NI_NAMEREQD);
+ if (error == 0) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(addr, "0", &hints, &res) == 0) {
+ freeaddrinfo(res);
+ strlcpy(s->s_hostname, "<bogus>", MAXHOSTNAMELEN);
+ imsg_compose(ibuf, IMSG_SMTP_HOSTNAME_ANSWER, 0, 0, -1,
+ s, sizeof(struct session));
+ break;
+ }
+ } else {
+ error = getnameinfo(sa, salen, addr, sizeof(addr),
+ NULL, 0, NI_NUMERICHOST);
+ }
+ strlcpy(s->s_hostname, addr, MAXHOSTNAMELEN);
+ imsg_compose(ibuf, IMSG_SMTP_HOSTNAME_ANSWER, 0, 0, -1,
+ s, sizeof(struct session));
+ break;
+ }
+ default:
+ log_debug("lka_dispatch_mfa: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+lka_dispatch_queue(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_QUEUE];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("lka_dispatch_queue: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+
+ case IMSG_LKA_ALIAS_LOOKUP: {
+ struct message *messagep;
+ struct alias *alias;
+ struct alias *remalias;
+ struct path *path;
+ struct aliaseslist aliases;
+ u_int8_t done = 0;
+ size_t nbiterations = 5;
+ int ret;
+
+ messagep = imsg.data;
+ path = &messagep->recipient;
+
+ if (path->flags & F_EXPANDED)
+ break;
+
+ TAILQ_INIT(&aliases);
+
+ if (path->flags & F_ALIAS) {
+ ret = aliases_get(env, &aliases, path->user);
+ }
+
+ if (path->flags & F_VIRTUAL) {
+ ret = aliases_virtual_get(env, &aliases, path);
+ }
+
+ if (! ret) {
+ /*
+ * Aliases could not be retrieved, this happens
+ * if the aliases database is regenerated while
+ * the message is being processed. It is not an
+ * error necessarily so just ignore this and it
+ * will be handled by the queue process.
+ */
+ imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep,
+ sizeof(struct message));
+ break;
+ }
+
+ /* First pass, figure out if some of the usernames that
+ * are in the list are actually aliases and expand them
+ * if they are. The resolution will be tried five times
+ * at most with an early exit if list did not change in
+ * a pass.
+ */
+ while (!done && nbiterations--) {
+ done = 1;
+ remalias = NULL;
+ TAILQ_FOREACH(alias, &aliases, entry) {
+ if (remalias) {
+ TAILQ_REMOVE(&aliases, remalias, entry);
+ free(remalias);
+ remalias = NULL;
+ }
+
+ if (alias->type == ALIAS_ADDRESS) {
+ if (aliases_virtual_get(env, &aliases, &alias->u.path)) {
+ done = 0;
+ remalias = alias;
+ }
+ }
+
+ else if (alias->type == ALIAS_USERNAME) {
+ if (aliases_get(env, &aliases, alias->u.username)) {
+ done = 0;
+ remalias = alias;
+ }
+ }
+ }
+ if (remalias) {
+ TAILQ_REMOVE(&aliases, remalias, entry);
+ free(remalias);
+ remalias = NULL;
+ }
+ }
+
+ /* Second pass, the list no longer contains aliases and
+ * the message can be sent back to queue process with a
+ * modified path.
+ */
+ TAILQ_FOREACH(alias, &aliases, entry) {
+ struct message message = *messagep;
+ lka_resolve_alias(env, ibuf, &message, alias);
+ imsg_compose(ibuf, IMSG_LKA_ALIAS_RESULT, 0, 0, -1,
+ &message, sizeof(struct message));
+ }
+
+ imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1,
+ messagep, sizeof(struct message));
+
+ while ((alias = TAILQ_FIRST(&aliases))) {
+ TAILQ_REMOVE(&aliases, alias, entry);
+ free(alias);
+ }
+ break;
+ }
+
+ case IMSG_LKA_FORWARD_LOOKUP: {
+ struct message *messagep;
+ struct aliaseslist aliases;
+ struct alias *alias;
+
+ messagep = imsg.data;
+
+ /* this is the tenth time the message has been forwarded
+ * internally, break out of the loop.
+ */
+ if (messagep->recipient.forwardcnt == 10) {
+ imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep,
+ sizeof(struct message));
+ break;
+ }
+ messagep->recipient.forwardcnt++;
+
+ TAILQ_INIT(&aliases);
+ if (! forwards_get(&aliases, messagep->recipient.pw_name)) {
+ messagep->recipient.flags |= F_NOFORWARD;
+ imsg_compose(ibuf, IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1, messagep, sizeof(struct message));
+ imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep,
+ sizeof(struct message));
+ break;
+ }
+
+ TAILQ_FOREACH(alias, &aliases, entry) {
+ struct message message = *messagep;
+ lka_resolve_alias(env, ibuf, &message, alias);
+ if (strcmp(messagep->recipient.pw_name, alias->u.username) == 0) {
+
+ message.recipient.flags |= F_FORWARDED;
+ }
+ imsg_compose(ibuf, IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1, &message, sizeof(struct message));
+ }
+
+ imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep, sizeof(struct message));
+
+ while ((alias = TAILQ_FIRST(&aliases))) {
+ TAILQ_REMOVE(&aliases, alias, entry);
+ free(alias);
+ }
+
+ break;
+ }
+
+ case IMSG_LKA_MX_LOOKUP: {
+ struct batch *batchp;
+ struct addrinfo hints, *res, *resp;
+ char **mx = NULL;
+ char *lmx[1];
+ size_t len, i, j;
+ int error;
+ u_int16_t port = 25;
+
+ batchp = imsg.data;
+
+ if (! IS_RELAY(batchp->rule.r_action))
+ err(1, "lka_dispatch_queue: inconsistent internal state");
+
+ if (batchp->rule.r_action == A_RELAY) {
+ log_debug("attempting to resolve %s", batchp->hostname);
+ len = getmxbyname(batchp->hostname, &mx);
+ if (len == 0) {
+ lmx[0] = batchp->hostname;
+ mx = lmx;
+ len = 1;
+ }
+ }
+ else if (batchp->rule.r_action == A_RELAYVIA) {
+ lmx[0] = batchp->rule.r_value.host.hostname;
+ port = batchp->rule.r_value.host.port;
+ log_debug("attempting to resolve %s:%d (forced)", lmx[0], port);
+ mx = lmx;
+ len = 1;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_protocol = IPPROTO_TCP;
+ for (i = j = 0; i < len; ++i) {
+ error = getaddrinfo(mx[i], NULL, &hints, &res);
+ if (error)
+ continue;
+
+ log_debug("resolving MX: %s", mx[i]);
+
+ for (resp = res; resp != NULL; resp = resp->ai_next) {
+ if (resp->ai_family == PF_INET) {
+ struct sockaddr_in *ssin;
+
+ batchp->ss[j] = *(struct sockaddr_storage *)resp->ai_addr;
+ ssin = (struct sockaddr_in *)&batchp->ss[j];
+ ssin->sin_port = htons(port);
+ ++j;
+ }
+ if (resp->ai_family == PF_INET6) {
+ struct sockaddr_in6 *ssin6;
+ batchp->ss[j] = *(struct sockaddr_storage *)resp->ai_addr;
+ ssin6 = (struct sockaddr_in6 *)&batchp->ss[j];
+ ssin6->sin6_port = htons(port);
+ ++j;
+ }
+ }
+
+ freeaddrinfo(res);
+ }
+
+ batchp->ss_cnt = j;
+ batchp->h_errno = 0;
+ if (j == 0)
+ batchp->h_errno = error;
+ imsg_compose(ibuf, IMSG_LKA_MX_LOOKUP, 0, 0, -1, batchp, sizeof(*batchp));
+
+ if (mx != lmx)
+ free(mx);
+
+ break;
+ }
+ default:
+ log_debug("lka_dispatch_queue: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+lka_shutdown(void)
+{
+ log_info("lookup agent exiting");
+ _exit(0);
+}
+
+void
+lka_setup_events(struct smtpd *env)
+{
+}
+
+void
+lka_disable_events(struct smtpd *env)
+{
+}
+
+pid_t
+lka(struct smtpd *env)
+{
+ pid_t pid;
+ struct passwd *pw;
+
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_PARENT, lka_dispatch_parent },
+ { PROC_MFA, lka_dispatch_mfa },
+ { PROC_QUEUE, lka_dispatch_queue },
+ { PROC_SMTP, lka_dispatch_smtp },
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("lka: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+// purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+ setproctitle("lookup agent");
+ smtpd_process = PROC_LKA;
+
+#ifndef DEBUG
+ 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("lka: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, lka_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, lka_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 4);
+
+ lka_setup_events(env);
+ event_dispatch();
+ lka_shutdown();
+
+ return (0);
+}
+
+int
+lka_verify_mail(struct smtpd *env, struct path *path)
+{
+ struct rule *r;
+ struct cond *cond;
+ struct map *map;
+ struct mapel *me;
+
+ TAILQ_FOREACH(r, env->sc_rules, r_entry) {
+ TAILQ_FOREACH(cond, &r->r_conditions, c_entry) {
+ if (cond->c_type == C_ALL) {
+ path->rule = *r;
+ if (r->r_action == A_MBOX ||
+ r->r_action == A_MAILDIR) {
+ return lka_resolve_mail(env, r, path);
+ }
+ return 1;
+ }
+
+ if (cond->c_type == C_DOM) {
+ cond->c_match = map_find(env, cond->c_map);
+ if (cond->c_match == NULL)
+ fatal("lka failed to lookup map.");
+
+ map = cond->c_match;
+ TAILQ_FOREACH(me, &map->m_contents, me_entry) {
+ if (strcasecmp(me->me_key.med_string,
+ path->domain) == 0) {
+ path->rule = *r;
+ if (r->r_action == A_MBOX ||
+ r->r_action == A_MAILDIR ||
+ r->r_action == A_EXT) {
+ return lka_resolve_mail(env, r, path);
+ }
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ path->rule.r_action = A_RELAY;
+ return 1;
+}
+
+int
+lka_verify_rcpt(struct smtpd *env, struct path *path, struct sockaddr_storage *ss)
+{
+ struct rule *r;
+ struct cond *cond;
+ struct map *map;
+ struct mapel *me;
+
+ TAILQ_FOREACH(r, env->sc_rules, r_entry) {
+
+ TAILQ_FOREACH(cond, &r->r_conditions, c_entry) {
+
+ if (cond->c_type == C_ALL) {
+ path->rule = *r;
+
+ if (! lka_check_source(env, r->r_sources, ss))
+ return 0;
+
+ if (r->r_action == A_MBOX ||
+ r->r_action == A_MAILDIR) {
+ return lka_resolve_rcpt(env, r, path);
+ }
+ return 1;
+ }
+
+ if (cond->c_type == C_DOM) {
+
+ cond->c_match = map_find(env, cond->c_map);
+ if (cond->c_match == NULL)
+ fatal("lka failed to lookup map.");
+
+ map = cond->c_match;
+ TAILQ_FOREACH(me, &map->m_contents, me_entry) {
+ if (strcasecmp(me->me_key.med_string,
+ path->domain) == 0) {
+ path->rule = *r;
+ if (! lka_check_source(env, r->r_sources, ss))
+ return 0;
+
+ if (IS_MAILBOX(r->r_action) ||
+ IS_EXT(r->r_action)) {
+ return lka_resolve_rcpt(env, r, path);
+ }
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int
+lka_resolve_mail(struct smtpd *env, struct rule *rule, struct path *path)
+{
+ char username[MAXLOGNAME];
+ struct passwd *pw;
+ char *p;
+
+ (void)strlcpy(username, path->user, MAXLOGNAME);
+
+ for (p = &username[0]; *p != '\0' && *p != '+'; ++p)
+ *p = tolower((int)*p);
+ *p = '\0';
+
+ if (aliases_virtual_exist(env, path))
+ path->flags |= F_VIRTUAL;
+ else if (aliases_exist(env, username))
+ path->flags |= F_ALIAS;
+ else {
+ pw = getpwnam(username);
+ if (pw == NULL)
+ return 0;
+ (void)strlcpy(path->pw_name, pw->pw_name, MAXLOGNAME);
+ if (lka_expand(path->rule.r_value.path, MAXPATHLEN, path) >=
+ MAXPATHLEN)
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+lka_resolve_rcpt(struct smtpd *env, struct rule *rule, struct path *path)
+{
+ char username[MAXLOGNAME];
+ struct passwd *pw;
+ char *p;
+
+ (void)strlcpy(username, path->user, MAXLOGNAME);
+
+ for (p = &username[0]; *p != '\0' && *p != '+'; ++p)
+ *p = tolower((int)*p);
+ *p = '\0';
+
+ if ((path->flags & F_EXPANDED) == 0 && aliases_virtual_exist(env, path))
+ path->flags |= F_VIRTUAL;
+ else if ((path->flags & F_EXPANDED) == 0 && aliases_exist(env, username))
+ path->flags |= F_ALIAS;
+ else {
+ pw = getpwnam(path->pw_name);
+ if (pw == NULL)
+ pw = getpwnam(username);
+ if (pw == NULL)
+ return 0;
+ (void)strlcpy(path->pw_name, pw->pw_name, MAXLOGNAME);
+ if (lka_expand(path->rule.r_value.path, MAXPATHLEN, path) >=
+ MAXPATHLEN) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int
+lka_expand(char *buf, size_t len, struct path *path)
+{
+ char *p, *pbuf;
+ struct rule r;
+ size_t ret;
+ struct passwd *pw;
+
+ bzero(r.r_value.path, MAXPATHLEN);
+ pbuf = r.r_value.path;
+
+ ret = 0;
+ for (p = path->rule.r_value.path; *p != '\0'; ++p) {
+ if (p == path->rule.r_value.path && *p == '~') {
+ if (*(p + 1) == '/' || *(p + 1) == '\0') {
+ pw = getpwnam(path->pw_name);
+ if (pw == NULL)
+ continue;
+
+ ret += strlcat(pbuf, pw->pw_dir, len);
+ if (ret >= len)
+ return ret;
+ pbuf += strlen(pw->pw_dir);
+ ++p;
+ continue;
+ }
+
+ if (*(p + 1) != '/') {
+ char username[MAXLOGNAME];
+ char *delim;
+
+ ret = strlcpy(username, p + 1, MAXLOGNAME);
+ delim = strchr(username, '/');
+ if (delim == NULL && ret >= MAXLOGNAME) {
+ continue;
+ }
+
+ if (delim != NULL) {
+ *delim = '\0';
+ }
+
+ pw = getpwnam(username);
+ if (pw == NULL)
+ continue;
+
+ ret += strlcat(pbuf, pw->pw_dir, len);
+ if (ret >= len)
+ return ret;
+ pbuf += strlen(pw->pw_dir);
+ p += strlen(username);
+ continue;
+ }
+ }
+ if (strncmp(p, "%a", 2) == 0) {
+ ret += strlcat(pbuf, path->user, len);
+ if (ret >= len)
+ return ret;
+ pbuf += strlen(path->user);
+ ++p;
+ continue;
+ }
+ if (strncmp(p, "%u", 2) == 0) {
+ ret += strlcat(pbuf, path->pw_name, len);
+ if (ret >= len)
+ return ret;
+ pbuf += strlen(path->pw_name);
+ ++p;
+ continue;
+ }
+ if (strncmp(p, "%d", 2) == 0) {
+ ret += strlcat(pbuf, path->domain, len);
+ if (ret >= len)
+ return ret;
+ pbuf += strlen(path->domain);
+ ++p;
+ continue;
+ }
+ if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'a') {
+ size_t idx;
+
+ idx = *(p+1) - '0';
+ if (idx < strlen(path->user))
+ *pbuf++ = path->user[idx];
+ p+=2;
+ ++ret;
+ continue;
+ }
+ if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'u') {
+ size_t idx;
+
+ idx = *(p+1) - '0';
+ if (idx < strlen(path->pw_name))
+ *pbuf++ = path->pw_name[idx];
+ p+=2;
+ ++ret;
+ continue;
+ }
+ if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'd') {
+ size_t idx;
+
+ idx = *(p+1) - '0';
+ if (idx < strlen(path->domain))
+ *pbuf++ = path->domain[idx];
+ p+=2;
+ ++ret;
+ continue;
+ }
+
+ *pbuf++ = *p;
+ ++ret;
+ }
+
+ memcpy(path->rule.r_value.path, r.r_value.path, ret);
+
+ return ret;
+}
+
+int
+lka_resolve_alias(struct smtpd *env, struct imsgbuf *ibuf, struct message *messagep, struct alias *alias)
+{
+ struct path *rpath = &messagep->recipient;
+
+ rpath->flags &= ~F_ALIAS;
+ rpath->flags |= F_EXPANDED;
+
+ switch (alias->type) {
+ case ALIAS_USERNAME:
+ if (strlcpy(rpath->pw_name, alias->u.username,
+ sizeof(rpath->pw_name)) >= sizeof(rpath->pw_name))
+ return 0;
+ lka_verify_rcpt(env, rpath, NULL);
+ break;
+
+ case ALIAS_FILENAME:
+ rpath->rule.r_action = A_FILENAME;
+ strlcpy(rpath->u.filename, alias->u.filename, MAXPATHLEN);
+ break;
+
+ case ALIAS_FILTER:
+ rpath->rule.r_action = A_EXT;
+ strlcpy(rpath->rule.r_value.command, alias->u.filter + 2, MAXPATHLEN);
+ rpath->rule.r_value.command[strlen(rpath->rule.r_value.command) - 1] = '\0';
+ break;
+
+ case ALIAS_ADDRESS:
+ *rpath = alias->u.path;
+ lka_verify_rcpt(env, rpath, NULL);
+ if (IS_MAILBOX(rpath->rule.r_action) ||
+ IS_EXT(rpath->rule.r_action))
+ messagep->type = T_MDA_MESSAGE;
+ else
+ messagep->type = T_MTA_MESSAGE;
+
+ break;
+ default:
+ /* ALIAS_INCLUDE cannot happen here, make gcc shut up */
+ break;
+ }
+ return 1;
+}
+
+int
+lka_check_source(struct smtpd *env, struct map *map, struct sockaddr_storage *ss)
+{
+ struct mapel *me;
+
+ if (ss == NULL) {
+ /* This happens when caller is part of an internal
+ * lookup (ie: alias resolved to a remote address)
+ */
+ return 1;
+ }
+
+ TAILQ_FOREACH(me, &map->m_contents, me_entry) {
+
+ if (ss->ss_family != me->me_key.med_addr.ss.ss_family)
+ continue;
+
+ if (ss->ss_len == me->me_key.med_addr.ss.ss_len)
+ continue;
+
+ if (lka_match_mask(ss, &me->me_key.med_addr))
+ return 1;
+
+ }
+ return 0;
+}
+
+int
+lka_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask)
+{
+ if (ss->ss_family == AF_INET) {
+ struct sockaddr_in *ssin = (struct sockaddr_in *)ss;
+ struct sockaddr_in *ssinmask = (struct sockaddr_in *)&ssmask->ss;
+
+ if ((ssin->sin_addr.s_addr & ssinmask->sin_addr.s_addr) ==
+ ssinmask->sin_addr.s_addr)
+ return (1);
+ return (0);
+ }
+
+ if (ss->ss_family == AF_INET6) {
+ struct in6_addr *in;
+ struct in6_addr *inmask;
+ struct in6_addr mask;
+ int i;
+
+ bzero(&mask, sizeof(mask));
+ for (i = 0; i < (128 - ssmask->masked) / 8; i++)
+ mask.s6_addr[i] = 0xff;
+ i = ssmask->masked % 8;
+ if (i)
+ mask.s6_addr[ssmask->masked / 8] = 0xff00 >> i;
+
+ in = &((struct sockaddr_in6 *)ss)->sin6_addr;
+ inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr;
+
+ for (i = 0; i < 16; i++) {
+ if ((in->s6_addr[i] & mask.s6_addr[i]) !=
+ inmask->s6_addr[i])
+ return (0);
+ }
+ return (1);
+ }
+
+ return (0);
+}
diff --git a/usr.sbin/smtpd/log.c b/usr.sbin/smtpd/log.c
new file mode 100644
index 00000000000..04b44c40b5c
--- /dev/null
+++ b/usr.sbin/smtpd/log.c
@@ -0,0 +1,161 @@
+/* $OpenBSD: log.c,v 1.1 2008/11/01 21:35:28 gilles Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@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 MIND, 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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <event.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "smtpd.h"
+
+int debug;
+
+void vlog(int, const char *, va_list);
+void logit(int, const char *, ...);
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_MAIL);
+
+ tzset();
+}
+
+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
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (debug > 1) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
diff --git a/usr.sbin/smtpd/map.c b/usr.sbin/smtpd/map.c
new file mode 100644
index 00000000000..2f72a4a434a
--- /dev/null
+++ b/usr.sbin/smtpd/map.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+struct map *
+map_findbyname(struct smtpd *env, const char *name)
+{
+ struct map *m;
+
+ TAILQ_FOREACH(m, env->sc_maps, m_entry) {
+ if (strcmp(m->m_name, name) == 0)
+ break;
+ }
+ return (m);
+}
+
+struct map *
+map_find(struct smtpd *env, objid_t id)
+{
+ struct map *m;
+
+ TAILQ_FOREACH(m, env->sc_maps, m_entry) {
+ if (m->m_id == id)
+ break;
+ }
+ return (m);
+}
diff --git a/usr.sbin/smtpd/mda.c b/usr.sbin/smtpd/mda.c
new file mode 100644
index 00000000000..5eaaf781c88
--- /dev/null
+++ b/usr.sbin/smtpd/mda.c
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <event.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void mda_shutdown(void);
+void mda_sig_handler(int, short, void *);
+void mda_dispatch_parent(int, short, void *);
+void mda_dispatch_queue(int, short, void *);
+void mda_setup_events(struct smtpd *);
+void mda_disable_events(struct smtpd *);
+void mda_timeout(int, short, void *);
+void mda_remove_message(struct smtpd *, struct batch *, struct message *x);
+
+void
+mda_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ mda_shutdown();
+ break;
+ default:
+ fatalx("mda_sig_handler: unexpected signal");
+ }
+}
+
+void
+mda_dispatch_parent(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_PARENT];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_mda: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_MDA_MAILBOX_FILE: {
+ struct batch *batchp;
+ struct message *messagep;
+ enum message_status status;
+
+ batchp = (struct batch *)imsg.data;
+ messagep = &batchp->message;
+ status = messagep->status;
+
+ batchp = batch_by_id(env, batchp->id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ messagep = message_by_id(env, batchp, messagep->id);
+ if (messagep == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+ messagep->status = status;
+
+ messagep->mboxfd = imsg_get_fd(ibuf, &imsg);
+ if (messagep->mboxfd == -1) {
+ mda_remove_message(env, batchp, messagep);
+ break;
+ }
+
+ batchp->message = *messagep;
+ imsg_compose(env->sc_ibufs[PROC_PARENT],
+ IMSG_PARENT_MESSAGE_OPEN, 0, 0, -1, batchp,
+ sizeof(struct batch));
+ break;
+ }
+
+ case IMSG_MDA_MESSAGE_FILE: {
+ struct batch *batchp;
+ struct message *messagep;
+ enum message_status status;
+ int (*store)(struct batch *, struct message *) = store_write_message;
+
+ batchp = (struct batch *)imsg.data;
+ messagep = &batchp->message;
+ status = messagep->status;
+
+ batchp = batch_by_id(env, batchp->id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ messagep = message_by_id(env, batchp, messagep->id);
+ if (messagep == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+ messagep->status = status;
+
+ messagep->messagefd = imsg_get_fd(ibuf, &imsg);
+ if (messagep->messagefd == -1) {
+ if (messagep->mboxfd != -1)
+ close(messagep->mboxfd);
+ mda_remove_message(env, batchp, messagep);
+ break;
+ }
+
+ /* If batch is a daemon message, override the default store function */
+ if (batchp->type & T_DAEMON_BATCH) {
+ store = store_write_daemon;
+ }
+
+ if (store_message(batchp, messagep, store)) {
+ if (batchp->message.recipient.rule.r_action == A_MAILDIR)
+ imsg_compose(env->sc_ibufs[PROC_PARENT],
+ IMSG_PARENT_MAILBOX_RENAME, 0, 0, -1, batchp,
+ sizeof(struct batch));
+ }
+
+ if (messagep->mboxfd != -1)
+ close(messagep->mboxfd);
+ if (messagep->messagefd != -1)
+ close(messagep->messagefd);
+
+ mda_remove_message(env, batchp, messagep);
+ break;
+ }
+ default:
+ log_debug("parent_dispatch_mda: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mda_dispatch_queue(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_QUEUE];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_queue: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CREATE_BATCH: {
+ struct batch *batchp;
+
+ batchp = calloc(1, sizeof (struct batch));
+ if (batchp == NULL)
+ fatal("calloc");
+ *batchp = *(struct batch *)imsg.data;
+ batchp->env = env;
+ batchp->flags = 0;
+
+ TAILQ_INIT(&batchp->messages);
+ SPLAY_INSERT(batchtree, &env->batch_queue, batchp);
+ break;
+ }
+
+ case IMSG_BATCH_APPEND: {
+ struct batch *batchp;
+ struct message *messagep;
+
+ messagep = calloc(1, sizeof (struct message));
+ if (messagep == NULL)
+ fatal("calloc");
+
+ *messagep = *(struct message *)imsg.data;
+ batchp = batch_by_id(env, messagep->batch_id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry);
+ break;
+ }
+
+ case IMSG_BATCH_CLOSE: {
+ struct batch lookup;
+ struct batch *batchp;
+ struct message *messagep;
+
+ lookup = *(struct batch *)imsg.data;
+ batchp = batch_by_id(env, lookup.id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ lookup = *batchp;
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ lookup.message = *messagep;
+ imsg_compose(env->sc_ibufs[PROC_PARENT],
+ IMSG_PARENT_MAILBOX_OPEN, 0, 0, -1, &lookup,
+ sizeof(struct batch));
+ }
+
+ break;
+ }
+
+ default:
+ log_debug("parent_dispatch_queue: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+
+void
+mda_shutdown(void)
+{
+ log_info("mail delivery agent exiting");
+ _exit(0);
+}
+
+void
+mda_setup_events(struct smtpd *env)
+{
+ struct timeval tv;
+
+ evtimer_set(&env->sc_ev, mda_timeout, env);
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+mda_disable_events(struct smtpd *env)
+{
+ evtimer_del(&env->sc_ev);
+}
+
+void
+mda_timeout(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct timeval tv;
+
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+pid_t
+mda(struct smtpd *env)
+{
+ pid_t pid;
+ struct passwd *pw;
+
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_PARENT, mda_dispatch_parent },
+ { PROC_QUEUE, mda_dispatch_queue }
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("mda: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("mda: chroot");
+ if (chdir("/") == -1)
+ fatal("mda: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("mail delivery agent");
+ smtpd_process = PROC_MDA;
+
+#ifndef DEBUG
+ 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("mda: cannot drop privileges");
+#endif
+
+ SPLAY_INIT(&env->batch_queue);
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, mda_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, mda_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 2);
+
+ mda_setup_events(env);
+ event_dispatch();
+ mda_shutdown();
+
+ return (0);
+}
+
+void
+mda_remove_message(struct smtpd *env, struct batch *batchp, struct message *messagep)
+{
+ imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_UPDATE, 0, 0,
+ -1, messagep, sizeof (struct message));
+
+ queue_remove_batch_message(env, batchp, messagep);
+}
diff --git a/usr.sbin/smtpd/mfa.c b/usr.sbin/smtpd/mfa.c
new file mode 100644
index 00000000000..5bfed1f68fe
--- /dev/null
+++ b/usr.sbin/smtpd/mfa.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <event.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void mfa_shutdown(void);
+void mfa_sig_handler(int, short, void *);
+void mfa_dispatch_parent(int, short, void *);
+void mfa_dispatch_smtp(int, short, void *);
+void mfa_dispatch_lka(int, short, void *);
+void mfa_setup_events(struct smtpd *);
+void mfa_disable_events(struct smtpd *);
+void mfa_timeout(int, short, void *);
+
+void
+mfa_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ mfa_shutdown();
+ break;
+ default:
+ fatalx("mfa_sig_handler: unexpected signal");
+ }
+}
+
+void
+mfa_dispatch_parent(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_PARENT];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_mfa: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("parent_dispatch_mfa: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mfa_dispatch_smtp(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_SMTP];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("mfa_dispatch_smtp: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_MFA_RPATH_SUBMIT: {
+ struct message *m;
+ struct submit_status ss;
+
+ m = imsg.data;
+ log_debug("mfa_dispatch_smtp: testing return path");
+ ss.id = m->id;
+ ss.code = 250;
+ ss.u.path = m->sender;
+
+ imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_LOOKUP_MAIL, 0, 0, -1,
+ &ss, sizeof(ss));
+ break;
+ }
+ case IMSG_MFA_RCPT_SUBMIT: {
+ struct message_recipient *mr;
+ struct submit_status ss;
+
+ mr = imsg.data;
+ log_debug("mfa_dispatch_smtp: testing forward path");
+ ss.id = mr->id;
+ ss.code = 250;
+ ss.u.path = mr->path;
+ ss.ss = mr->ss;
+
+ imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_LOOKUP_RCPT, 0, 0, -1,
+ &ss, sizeof(ss));
+ break;
+ }
+ case IMSG_MFA_DATA_SUBMIT:
+ break;
+ default:
+ log_debug("mfa_dispatch_smtp: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mfa_dispatch_lka(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_LKA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("mfa_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_MFA_LOOKUP_MAIL: {
+ struct submit_status *ss;
+
+ ss = imsg.data;
+ imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_RPATH_SUBMIT, 0, 0, -1,
+ ss, sizeof(*ss));
+ break;
+ }
+ case IMSG_MFA_LOOKUP_RCPT: {
+ struct submit_status *ss;
+
+ ss = imsg.data;
+ imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_MFA_RCPT_SUBMIT, 0, 0, -1,
+ ss, sizeof(*ss));
+ break;
+ }
+ default:
+ log_debug("mfa_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mfa_shutdown(void)
+{
+ log_info("mail filter exiting");
+ _exit(0);
+}
+
+void
+mfa_setup_events(struct smtpd *env)
+{
+ struct timeval tv;
+
+ evtimer_set(&env->sc_ev, mfa_timeout, env);
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+mfa_disable_events(struct smtpd *env)
+{
+ evtimer_del(&env->sc_ev);
+}
+
+void
+mfa_timeout(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct timeval tv;
+
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+pid_t
+mfa(struct smtpd *env)
+{
+ pid_t pid;
+ struct passwd *pw;
+
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_PARENT, mfa_dispatch_parent },
+ { PROC_SMTP, mfa_dispatch_smtp },
+ { PROC_LKA, mfa_dispatch_lka },
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("mfa: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+// purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("mfa: chroot");
+ if (chdir("/") == -1)
+ fatal("mfa: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("mail filter agent");
+ smtpd_process = PROC_MFA;
+
+#ifndef DEBUG
+ 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("mfa: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, mfa_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, mfa_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 3);
+
+ mfa_setup_events(env);
+ event_dispatch();
+ mfa_shutdown();
+
+ return (0);
+}
+
+int
+msg_cmp(struct message *m1, struct message *m2)
+{
+ /*
+ * do not return u_int64_t's
+ */
+ if (m1->id - m2->id > 0)
+ return (1);
+ else if (m1->id - m2->id < 0)
+ return (-1);
+ else
+ return (0);
+}
+
+SPLAY_GENERATE(msgtree, message, nodes, msg_cmp);
diff --git a/usr.sbin/smtpd/mta.c b/usr.sbin/smtpd/mta.c
new file mode 100644
index 00000000000..03bb668bea6
--- /dev/null
+++ b/usr.sbin/smtpd/mta.c
@@ -0,0 +1,786 @@
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void mta_shutdown(void);
+void mta_sig_handler(int, short, void *);
+void mta_dispatch_parent(int, short, void *);
+void mta_dispatch_queue(int, short, void *);
+void mta_setup_events(struct smtpd *);
+void mta_disable_events(struct smtpd *);
+void mta_timeout(int, short, void *);
+void mta_write(int, short, void *);
+int mta_connect(struct batch *);
+void mta_read_handler(struct bufferevent *, void *);
+void mta_write_handler(struct bufferevent *, void *);
+void mta_error_handler(struct bufferevent *, short, void *);
+int mta_reply_handler(struct bufferevent *, void *);
+void mta_batch_update_queue(struct batch *);
+
+void
+mta_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ mta_shutdown();
+ break;
+ default:
+ fatalx("mta_sig_handler: unexpected signal");
+ }
+}
+
+void
+mta_dispatch_parent(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_PARENT];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_mta: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("parent_dispatch_mta: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mta_dispatch_queue(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_QUEUE];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_mta: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CREATE_BATCH: {
+ struct batch *batchp;
+
+ batchp = calloc(1, sizeof (struct batch));
+ if (batchp == NULL)
+ err(1, "calloc");
+
+ *batchp = *(struct batch *)imsg.data;
+ batchp->ss_off = 0;
+ batchp->env = env;
+ batchp->flags = 0;
+
+ TAILQ_INIT(&batchp->messages);
+ SPLAY_INSERT(batchtree, &env->batch_queue, batchp);
+
+ break;
+ }
+ case IMSG_BATCH_APPEND: {
+ struct batch *batchp;
+ struct message *messagep;
+
+ messagep = calloc(1, sizeof (struct message));
+ if (messagep == NULL)
+ fatal("calloc");
+
+ *messagep = *(struct message *)imsg.data;
+
+ batchp = batch_by_id(env, messagep->batch_id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry);
+
+ break;
+ }
+ case IMSG_BATCH_CLOSE: {
+ struct batch *batchp;
+
+ batchp = (struct batch *)imsg.data;
+ batchp = batch_by_id(env, batchp->id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+
+ batchp->flags |= F_BATCH_COMPLETE;
+
+ while (! mta_connect(batchp)) {
+ if (batchp->ss_off == batchp->ss_cnt) {
+ break;
+ }
+ }
+ break;
+ }
+ case IMSG_QUEUE_MESSAGE_FD: {
+ struct batch *batchp;
+ int fd;
+
+ if ((fd = imsg_get_fd(ibuf, &imsg)) == -1) {
+ /* NEEDS_FIX - unsure yet how it must be handled */
+ errx(1, "imsg_get_fd");
+ }
+
+ batchp = (struct batch *)imsg.data;
+ batchp = batch_by_id(env, batchp->id);
+
+ if ((batchp->messagefp = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen");
+
+ evbuffer_add_printf(batchp->bev->output, "DATA\r\n");
+
+ bufferevent_enable(batchp->bev, EV_WRITE|EV_READ);
+ break;
+ }
+ default:
+ log_debug("parent_dispatch_mta: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+mta_shutdown(void)
+{
+ log_info("mail transfer agent exiting");
+ _exit(0);
+}
+
+void
+mta_setup_events(struct smtpd *env)
+{
+ struct timeval tv;
+
+ evtimer_set(&env->sc_ev, mta_timeout, env);
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+mta_disable_events(struct smtpd *env)
+{
+ evtimer_del(&env->sc_ev);
+}
+
+void
+mta_timeout(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct timeval tv;
+
+ tv.tv_sec = 3;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+pid_t
+mta(struct smtpd *env)
+{
+ pid_t pid;
+
+ struct passwd *pw;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_QUEUE, mta_dispatch_queue }
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("mta: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("mta: chroot");
+ if (chdir("/") == -1)
+ fatal("mta: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("mail transfer agent");
+ smtpd_process = PROC_MTA;
+
+#ifndef DEBUG
+ 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("mta: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, mta_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, mta_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 1);
+
+ SPLAY_INIT(&env->batch_queue);
+
+ mta_setup_events(env);
+ event_dispatch();
+ mta_shutdown();
+
+ return (0);
+}
+
+/* shamelessly ripped usr.sbin/relayd/check_tcp.c ;) */
+int
+mta_connect(struct batch *batchp)
+{
+ int s;
+ int type;
+ struct linger lng;
+ struct sockaddr_in ssin;
+ struct sockaddr_in6 ssin6;
+
+ if ((s = socket(batchp->ss[batchp->ss_off].ss_family, SOCK_STREAM, 0)) == -1) {
+ goto bad;
+ }
+
+ bzero(&lng, sizeof(lng));
+ if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof (lng)) == -1) {
+ goto bad;
+ }
+
+ type = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &type, sizeof (type)) == -1) {
+ goto bad;
+ }
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
+ goto bad;
+ }
+
+ if (batchp->ss[batchp->ss_off].ss_family == PF_INET) {
+ ssin = *(struct sockaddr_in *)&batchp->ss[batchp->ss_off];
+ if (connect(s, (struct sockaddr *)&ssin, sizeof(struct sockaddr_in)) == -1) {
+ if (errno != EINPROGRESS) {
+ goto bad;
+ }
+ }
+ }
+
+ if (batchp->ss[batchp->ss_off].ss_family == PF_INET6) {
+ ssin6 = *(struct sockaddr_in6 *)&batchp->ss[batchp->ss_off];
+ if (connect(s, (struct sockaddr *)&ssin6, sizeof(struct sockaddr_in6)) == -1) {
+ if (errno != EINPROGRESS) {
+ goto bad;
+ }
+ }
+ }
+
+ batchp->tv.tv_sec = SMTPD_CONNECT_TIMEOUT;
+ batchp->tv.tv_usec = 0;
+ batchp->peerfd = s;
+ event_set(&batchp->ev, s, EV_TIMEOUT|EV_WRITE, mta_write, batchp);
+ event_add(&batchp->ev, &batchp->tv);
+
+ return 1;
+
+bad:
+ batchp->ss_off++;
+ close(s);
+ return 0;
+}
+
+void
+mta_write(int s, short event, void *arg)
+{
+ struct batch *batchp = arg;
+ int ret;
+
+ if (event == EV_TIMEOUT) {
+ batchp->ss_off++;
+ close(s);
+ if (batchp->bev) {
+ bufferevent_free(batchp->bev);
+ batchp->bev = NULL;
+ }
+ strlcpy(batchp->errorline, "connection timed-out.", STRLEN);
+
+ ret = 0;
+ while (batchp->ss_off < batchp->ss_cnt &&
+ (ret = mta_connect(batchp)) == 0) {
+ continue;
+ }
+ if (ret)
+ return;
+
+ mta_batch_update_queue(batchp);
+ return;
+ }
+
+ batchp->bev = bufferevent_new(s, mta_read_handler, mta_write_handler,
+ mta_error_handler, batchp);
+
+ if (batchp->bev == NULL) {
+ mta_batch_update_queue(batchp);
+ close(s);
+ return;
+ }
+
+ bufferevent_enable(batchp->bev, EV_READ|EV_WRITE);
+}
+
+void
+mta_read_handler(struct bufferevent *bev, void *arg)
+{
+ while (mta_reply_handler(bev, arg))
+ ;
+}
+
+int
+mta_reply_handler(struct bufferevent *bev, void *arg)
+{
+ struct batch *batchp = arg;
+ struct smtpd *env = batchp->env;
+ struct message *messagep = NULL;
+ char *line;
+ int i;
+ int code;
+#define F_ISINFO 0x1
+#define F_ISPROTOERROR 0x2
+ char codebuf[4];
+ char *errstr;
+ int flags = 0;
+
+ line = evbuffer_readline(bev->input);
+ if (line == NULL) {
+ bufferevent_enable(bev, EV_READ|EV_WRITE);
+ return 0;
+ }
+
+ line[strcspn(line, "\r")] = '\0';
+
+ bufferevent_enable(bev, EV_READ|EV_WRITE);
+
+ log_debug("remote server sent: [%s]", line);
+
+ strlcpy(codebuf, line, sizeof codebuf);
+ code = strtonum(codebuf, 0, UINT16_MAX, (const char **)&errstr);
+ if (errstr || code < 100) {
+ /* Server sent invalid line, protocol error */
+ batchp->status |= S_BATCH_PERMFAILURE;
+ strlcpy(batchp->errorline, line, STRLEN);
+ mta_batch_update_queue(batchp);
+ return 0;
+ }
+
+ if (line[3] == '-') {
+ return 1;
+ }
+
+ switch (code) {
+ case 250:
+ if (batchp->state == S_DONE) {
+ mta_batch_update_queue(batchp);
+ return 0;
+ }
+ break;
+
+ case 220:
+ evbuffer_add_printf(batchp->bev->output, "EHLO %s\r\n", env->sc_hostname);
+ batchp->state = S_GREETED;
+ return 1;
+
+ case 421:
+ case 450:
+ case 451:
+ batchp->status |= S_BATCH_TEMPFAILURE;
+ strlcpy(batchp->errorline, line, STRLEN);
+ mta_batch_update_queue(batchp);
+ return 0;
+
+ /* The following codes are state dependant and will cause
+ * a batch rejection if returned at the wrong state.
+ */
+ case 530:
+ case 550:
+ if (batchp->state == S_RCPT) {
+ batchp->messagep->status = (S_MESSAGE_REJECTED|S_MESSAGE_PERMFAILURE);
+ strlcpy(batchp->messagep->session_errorline, line, STRLEN);
+ break;
+ }
+ case 354:
+ if (batchp->state == S_RCPT && batchp->messagep == NULL) {
+ batchp->state = S_DATA;
+ break;
+ }
+
+ case 221:
+ if (batchp->state == S_DONE) {
+ mta_batch_update_queue(batchp);
+ return 0;
+ }
+
+ case 552:
+ case 553:
+ flags |= F_ISPROTOERROR;
+ default:
+ /* Server sent code we know nothing about, error */
+ if (!(flags & F_ISPROTOERROR))
+ log_debug("Ouch, SMTP session returned unhandled %d status.", code);
+
+ batchp->status |= S_BATCH_PERMFAILURE;
+ strlcpy(batchp->errorline, line, STRLEN);
+ mta_batch_update_queue(batchp);
+ return 0;
+ }
+
+
+ switch (batchp->state) {
+ case S_GREETED: {
+ char *user;
+ char *domain;
+
+ if (batchp->type & T_DAEMON_BATCH) {
+ user = "MAILER-DAEMON";
+ domain = env->sc_hostname;
+ }
+ else {
+ messagep = TAILQ_FIRST(&batchp->messages);
+ user = messagep->sender.user;
+ domain = messagep->sender.domain;
+ }
+
+ if (user[0] == '\0' && domain[0] == '\0')
+ evbuffer_add_printf(batchp->bev->output, "MAIL FROM:<>\r\n");
+ else
+ evbuffer_add_printf(batchp->bev->output, "MAIL FROM:<%s@%s>\r\n", user, domain);
+ batchp->state = S_MAIL;
+
+ break;
+ }
+
+ case S_MAIL:
+ batchp->state = S_RCPT;
+
+ case S_RCPT: {
+ char *user;
+ char *domain;
+
+ /* Is this the first RCPT ? */
+ if (batchp->messagep == NULL)
+ messagep = TAILQ_FIRST(&batchp->messages);
+ else {
+ /* We already had a RCPT, mark is as accepted and
+ * fetch next one from queue if we aren't dealing
+ * with a daemon batch.
+ */
+ if (batchp->type & T_DAEMON_BATCH)
+ messagep = NULL;
+ else {
+ messagep = batchp->messagep;
+ if ((messagep->status & S_MESSAGE_REJECTED) == 0)
+ messagep->status = S_MESSAGE_ACCEPTED;
+ messagep = TAILQ_NEXT(batchp->messagep, entry);
+ }
+ }
+ batchp->messagep = messagep;
+
+ if (messagep) {
+ if (batchp->type & T_DAEMON_BATCH) {
+ user = messagep->sender.user;
+ domain = messagep->sender.domain;
+ }
+ else {
+ user = messagep->recipient.user;
+ domain = messagep->recipient.domain;
+ }
+ evbuffer_add_printf(batchp->bev->output, "RCPT TO:<%s@%s>\r\n", user, domain);
+ }
+ else {
+ /* Do we have at least one accepted recipient ? */
+ if ((batchp->type & T_DAEMON_BATCH) == 0) {
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ if (messagep->status & S_MESSAGE_ACCEPTED)
+ break;
+ }
+ if (messagep == NULL) {
+ batchp->status |= S_BATCH_PERMFAILURE;
+ mta_batch_update_queue(batchp);
+ return 0;
+ }
+ }
+
+ bufferevent_disable(batchp->bev, EV_WRITE|EV_READ);
+ imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_FD,
+ 0, 0, -1, batchp, sizeof(*batchp));
+ }
+ break;
+ }
+
+ case S_DATA: {
+ bufferevent_enable(batchp->bev, EV_READ|EV_WRITE);
+
+ evbuffer_add_printf(batchp->bev->output,
+ "Received: from %s (%s [%s])\r\n"
+ "\tby %s with ESMTP id %s\r\n",
+ "localhost", "localhost", "127.0.0.1",
+ "", batchp->message_id);
+
+ evbuffer_add_printf(batchp->bev->output, "X-OpenSMTPD: experiment\r\n");
+
+ if (batchp->type & T_DAEMON_BATCH) {
+ evbuffer_add_printf(batchp->bev->output,
+ "Hi !\r\n\r\n"
+ "This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail it is\r\n"
+ "just a notification to let you know that an error has occured.\r\n\r\n");
+
+ if (batchp->status & S_BATCH_PERMFAILURE) {
+ evbuffer_add_printf(batchp->bev->output,
+ "You ran into a PERMANENT FAILURE, which means that the e-mail can't\r\n"
+ "be delivered to the remote host no matter how much I'll try.\r\n\r\n"
+ "Diagnostic:\r\n"
+ "%s\r\n\r\n", batchp->errorline);
+ }
+
+ if (batchp->status & S_BATCH_TEMPFAILURE) {
+ evbuffer_add_printf(batchp->bev->output,
+ "You ran into a TEMPORARY FAILURE, which means that the e-mail can't\r\n"
+ "be delivered right now, but could be deliberable at a later time. I\r\n"
+ "will attempt until it succeeds for the next four days, then let you\r\n"
+ "know if it didn't work out.\r\n"
+ "Diagnostic:\r\n"
+ "%s\r\n\r\n", batchp->errorline);
+ }
+
+ i = 0;
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ if (messagep->status & S_MESSAGE_TEMPFAILURE) {
+ if (i == 0) {
+ evbuffer_add_printf(batchp->bev->output,
+ "The following recipients caused a temporary failure:\r\n");
+ ++i;
+ }
+
+ evbuffer_add_printf(batchp->bev->output,
+ "\t<%s@%s>:\r\n%s\r\n\r\n",
+ messagep->recipient.user, messagep->recipient.domain, messagep->session_errorline);
+ }
+ }
+
+ i = 0;
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ if (messagep->status & S_MESSAGE_PERMFAILURE) {
+ if (i == 0) {
+ evbuffer_add_printf(batchp->bev->output,
+ "The following recipients caused a permanent failure:\r\n");
+ ++i;
+ }
+
+ evbuffer_add_printf(batchp->bev->output,
+ "\t<%s@%s>:\r\n%s\r\n\r\n",
+ messagep->recipient.user, messagep->recipient.domain, messagep->session_errorline);
+ }
+ }
+
+ evbuffer_add_printf(batchp->bev->output,
+ "Below is a copy of the original message:\r\n\r\n");
+ }
+
+ break;
+ }
+ case S_DONE:
+ evbuffer_add_printf(batchp->bev->output, "QUIT\r\n");
+ batchp->state = S_QUIT;
+ break;
+
+ default:
+ log_info("unknown command: %d", batchp->state);
+ }
+
+ return 1;
+}
+
+void
+mta_write_handler(struct bufferevent *bev, void *arg)
+{
+ struct batch *batchp = arg;
+ char *buf, *lbuf;
+ size_t len;
+
+ if (batchp->state == S_QUIT) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+ close(batchp->peerfd);
+ return;
+ }
+
+ /* Progressively fill the output buffer with data */
+ if (batchp->state == S_DATA) {
+
+ lbuf = NULL;
+ if ((buf = fgetln(batchp->messagefp, &len))) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ else {
+ if ((lbuf = malloc(len + 1)) == NULL)
+ err(1, "malloc");
+ memcpy(lbuf, buf, len);
+ lbuf[len] = '\0';
+ buf = lbuf;
+ }
+ evbuffer_add_printf(batchp->bev->output, "%s\r\n", buf);
+ free(lbuf);
+ }
+ else {
+ evbuffer_add_printf(batchp->bev->output, ".\r\n");
+ batchp->state = S_DONE;
+ fclose(batchp->messagefp);
+ batchp->messagefp = NULL;
+ }
+ }
+ bufferevent_enable(batchp->bev, EV_READ|EV_WRITE);
+}
+
+void
+mta_error_handler(struct bufferevent *bev, short error, void *arg)
+{
+ struct batch *batchp = arg;
+ if (error & (EVBUFFER_TIMEOUT|EVBUFFER_EOF)) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+ close(batchp->peerfd);
+ return;
+ }
+}
+
+void
+mta_batch_update_queue(struct batch *batchp)
+{
+ struct smtpd *env = batchp->env;
+ struct message *messagep;
+
+ while ((messagep = TAILQ_FIRST(&batchp->messages)) != NULL) {
+
+ if (batchp->status & S_BATCH_PERMFAILURE) {
+ messagep->status |= S_MESSAGE_PERMFAILURE;
+ }
+
+ if (batchp->status & S_BATCH_TEMPFAILURE) {
+ if (messagep->status != S_MESSAGE_PERMFAILURE)
+ messagep->status |= S_MESSAGE_TEMPFAILURE;
+ }
+
+ imsg_compose(env->sc_ibufs[PROC_QUEUE],
+ IMSG_QUEUE_MESSAGE_UPDATE, 0, 0, -1, messagep,
+ sizeof(struct message));
+ TAILQ_REMOVE(&batchp->messages, messagep, entry);
+ free(messagep);
+ }
+
+ SPLAY_REMOVE(batchtree, &env->batch_queue, batchp);
+
+ if (batchp->messagefp)
+ fclose(batchp->messagefp);
+
+ if (batchp->bev)
+ bufferevent_free(batchp->bev);
+
+ free(batchp);
+}
diff --git a/usr.sbin/smtpd/parse.y b/usr.sbin/smtpd/parse.y
new file mode 100644
index 00000000000..29c4b340deb
--- /dev/null
+++ b/usr.sbin/smtpd/parse.y
@@ -0,0 +1,1377 @@
+/*
+ * Copyright (c) 2008 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/time.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <pwd.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file, *topfile;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+struct smtpd *conf = NULL;
+static int errors = 0;
+
+objid_t last_map_id = 0;
+struct map *map = NULL;
+struct rule *rule = NULL;
+struct mapel_list *contents = NULL;
+
+struct listener *host_v4(const char *, in_port_t);
+struct listener *host_v6(const char *, in_port_t);
+int host_dns(const char *, struct listenerlist *,
+ int, in_port_t, u_int8_t);
+int host(const char *, struct listenerlist *,
+ int, in_port_t, u_int8_t);
+
+typedef struct {
+ union {
+ int64_t number;
+ objid_t object;
+ struct timeval tv;
+ struct cond *cond;
+ char *string;
+ struct host *host;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token QUEUE INTERVAL LISTEN ON ALL PORT USE
+%token MAP TYPE HASH LIST SINGLE SSL SSMTP CERTIFICATE
+%token DNS DB TFILE EXTERNAL DOMAIN CONFIG SOURCE
+%token RELAY VIA DELIVER TO MAILDIR MBOX HOSTNAME
+%token ACCEPT REJECT INCLUDE NETWORK ERROR MDA FROM FOR
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.map> map
+%type <v.number> quantifier decision port ssmtp from
+%type <v.cond> condition
+%type <v.tv> interval
+%type <v.object> mapref
+%type <v.string> certname
+
+%%
+
+grammar : /* empty */
+ | grammar '\n'
+ | grammar include '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar map '\n'
+ | grammar rule '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+comma : ','
+ | nl
+ | /* empty */
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+quantifier : /* empty */ { $$ = 1; }
+ | 'm' { $$ = 60; }
+ | 'h' { $$ = 3600; }
+ | 'd' { $$ = 86400; }
+ ;
+
+interval : NUMBER quantifier {
+ if ($1 < 0) {
+ yyerror("invalid interval: %d\n", $1);
+ YYERROR;
+ }
+ $$.tv_usec = 0;
+ $$.tv_sec = $1 * $2;
+ }
+
+
+port : PORT STRING {
+ struct servent *servent;
+
+ servent = getservbyname($2, "tcp");
+ if (servent == NULL) {
+ yyerror("port %s is invalid", $2);
+ free($2);
+ YYERROR;
+ }
+ $$ = servent->s_port;
+ free($2);
+ }
+ | PORT NUMBER {
+ if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+ yyerror("invalid port: %d", $2);
+ YYERROR;
+ }
+ $$ = htons($2);
+ }
+ | /* empty */ {
+ $$ = 0;
+ }
+ ;
+
+certname : USE CERTIFICATE STRING {
+ if (($$ = strdup($3)) == NULL)
+ fatal(NULL);
+ free($3);
+ }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+ssmtp : SSMTP { $$ = 1; }
+ | /* empty */ { $$ = 0; }
+ ;
+
+main : QUEUE INTERVAL interval {
+ conf->sc_qintval = $3;
+ }
+ | ssmtp LISTEN ON STRING port certname {
+ char *cert;
+ u_int8_t flags;
+
+ if ($5 == 0) {
+ if ($1)
+ $5 = htons(487);
+ else
+ $5 = htons(25);
+ }
+ cert = ($6 != NULL) ? $6 : $4;
+
+ flags = 0;
+ if (ssl_load_certfile(conf, cert) < 0) {
+ log_warnx("could not load cert: %s", cert);
+ if ($1 || $6 != NULL) {
+ yyerror("cannot load certificate: %s",
+ cert);
+ free($6);
+ free($4);
+ YYERROR;
+ }
+ }
+ else {
+ if ($1)
+ flags = F_SSMTP;
+ else
+ flags = F_STARTTLS;
+ }
+
+ if (host($4, &conf->sc_listeners,
+ MAX_LISTEN, $5, flags) <= 0) {
+ yyerror("invalid virtual ip: %s", $4);
+ free($6);
+ free($4);
+ YYERROR;
+ }
+ free($6);
+ free($4);
+ }
+ | HOSTNAME STRING {
+ if (strlcpy(conf->sc_hostname, $2,
+ sizeof(conf->sc_hostname)) >=
+ sizeof(conf->sc_hostname)) {
+ yyerror("hostname truncated");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+maptype : SINGLE { map->m_type = T_SINGLE; }
+ | LIST { map->m_type = T_LIST; }
+ | HASH { map->m_type = T_HASH; }
+ ;
+
+mapsource : DNS { map->m_src = S_DNS; }
+ | TFILE { map->m_src = S_FILE; }
+ | DB STRING {
+ map->m_src = S_DB;
+ if (strlcpy(map->m_config, $2, MAXPATHLEN)
+ >= MAXPATHLEN)
+ err(1, "pathname too long");
+ }
+ | EXTERNAL { map->m_src = S_EXT; }
+ ;
+
+mapopt : TYPE maptype
+ | SOURCE mapsource
+ | CONFIG STRING {
+ }
+ ;
+
+mapopts_l : mapopts_l mapopt nl
+ | mapopt optnl
+ ;
+
+map : MAP STRING {
+ struct map *m;
+
+ TAILQ_FOREACH(m, conf->sc_maps, m_entry)
+ if (strcmp(m->m_name, $2) == 0)
+ break;
+
+ if (m != NULL) {
+ yyerror("map %s defined twice", $2);
+ free($2);
+ YYERROR;
+ }
+ if ((m = calloc(1, sizeof(*m))) == NULL)
+ fatal("out of memory");
+ if (strlcpy(m->m_name, $2, sizeof(m->m_name)) >=
+ sizeof(m->m_name)) {
+ yyerror("map name truncated");
+ free(m);
+ free($2);
+ YYERROR;
+ }
+
+ m->m_id = last_map_id++;
+ m->m_type = T_SINGLE;
+
+ if (m->m_id == INT_MAX) {
+ yyerror("too many maps defined");
+ free($2);
+ free(m);
+ YYERROR;
+ }
+ map = m;
+ } '{' optnl mapopts_l '}' {
+ if (map->m_src == S_NONE) {
+ yyerror("map %s has no source defined");
+ free(map);
+ map = NULL;
+ YYERROR;
+ }
+ TAILQ_INSERT_TAIL(conf->sc_maps, map, m_entry);
+ map = NULL;
+ }
+ ;
+
+keyval : STRING '=' '>' STRING {
+ struct mapel *me;
+
+ if ((me = calloc(1, sizeof(*me))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(me->me_key.med_string, $1,
+ sizeof(me->me_key.med_string)) >=
+ sizeof(me->me_key.med_string) ||
+ strlcpy(me->me_val.med_string, $4,
+ sizeof(me->me_val.med_string)) >=
+ sizeof(me->me_val.med_string)) {
+ yyerror("map elements too long: %s, %s",
+ $1, $4);
+ free(me);
+ free($1);
+ free($4);
+ YYERROR;
+ }
+ free($1);
+ free($4);
+
+ TAILQ_INSERT_TAIL(contents, me, me_entry);
+ }
+
+keyval_list : keyval
+ | keyval comma keyval_list
+ ;
+
+stringel : STRING {
+ struct mapel *me;
+ int bits;
+ struct sockaddr_in ssin;
+ struct sockaddr_in6 ssin6;
+
+ if ((me = calloc(1, sizeof(*me))) == NULL)
+ fatal("out of memory");
+
+ /* Attempt detection of $1 format */
+ if (strchr($1, '/') != NULL) {
+ /* Dealing with a netmask */
+ bzero(&ssin, sizeof(struct sockaddr_in));
+ bits = inet_net_pton(AF_INET, $1, &ssin.sin_addr, sizeof(struct in_addr));
+ if (bits != -1) {
+ ssin.sin_family = AF_INET;
+ me->me_key.med_addr.masked = bits;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin;
+ }
+ else {
+ bzero(&ssin6, sizeof(struct sockaddr_in6));
+ bits = inet_net_pton(AF_INET6, $1, &ssin6.sin6_addr, sizeof(struct in6_addr));
+ if (bits == -1)
+ err(1, "inet_net_pton");
+ ssin6.sin6_family = AF_INET6;
+ me->me_key.med_addr.masked = bits;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin6;
+ }
+ }
+ else {
+ /* IP address ? */
+ if (inet_pton(AF_INET, $1, &ssin.sin_addr) == 1) {
+ ssin.sin_family = AF_INET;
+ me->me_key.med_addr.masked = 0;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin;
+ }
+ else if (inet_pton(AF_INET6, $1, &ssin6.sin6_addr) == 1) {
+ ssin6.sin6_family = AF_INET6;
+ me->me_key.med_addr.masked = 0;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin6;
+ }
+ else {
+ /* either a hostname or a value unrelated to network */
+ if (strlcpy(me->me_key.med_string, $1,
+ sizeof(me->me_key.med_string)) >=
+ sizeof(me->me_key.med_string)) {
+ yyerror("map element too long: %s", $1);
+ free(me);
+ free($1);
+ YYERROR;
+ }
+ }
+ }
+ free($1);
+ TAILQ_INSERT_TAIL(contents, me, me_entry);
+ }
+ ;
+
+string_list : stringel
+ | stringel comma string_list
+ ;
+
+mapref : STRING {
+ struct map *m;
+ struct mapel *me;
+ int bits;
+ struct sockaddr_in ssin;
+ struct sockaddr_in6 ssin6;
+
+ if ((m = calloc(1, sizeof(*m))) == NULL)
+ fatal("out of memory");
+ m->m_id = last_map_id++;
+ if (m->m_id == INT_MAX) {
+ yyerror("too many maps defined");
+ free(m);
+ YYERROR;
+ }
+ snprintf(m->m_name, STRLEN, "<dynamic(%u)>", m->m_id);
+ m->m_flags |= F_DYNAMIC|F_USED;
+ m->m_type = T_SINGLE;
+
+ TAILQ_INIT(&m->m_contents);
+
+ if ((me = calloc(1, sizeof(*me))) == NULL)
+ fatal("out of memory");
+
+ /* Attempt detection of $1 format */
+ if (strchr($1, '/') != NULL) {
+ /* Dealing with a netmask */
+ bzero(&ssin, sizeof(struct sockaddr_in));
+ bits = inet_net_pton(AF_INET, $1, &ssin.sin_addr, sizeof(struct in_addr));
+ if (bits != -1) {
+ ssin.sin_family = AF_INET;
+ me->me_key.med_addr.masked = bits;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin;
+ }
+ else {
+ bzero(&ssin6, sizeof(struct sockaddr_in6));
+ bits = inet_net_pton(AF_INET6, $1, &ssin6.sin6_addr, sizeof(struct in6_addr));
+ if (bits == -1)
+ err(1, "inet_net_pton");
+ ssin6.sin6_family = AF_INET6;
+ me->me_key.med_addr.masked = bits;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin6;
+ }
+ }
+ else {
+ /* IP address ? */
+ if (inet_pton(AF_INET, $1, &ssin.sin_addr) == 1) {
+ ssin.sin_family = AF_INET;
+ me->me_key.med_addr.masked = 0;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin;
+ }
+ else if (inet_pton(AF_INET6, $1, &ssin6.sin6_addr) == 1) {
+ ssin6.sin6_family = AF_INET6;
+ me->me_key.med_addr.masked = 0;
+ me->me_key.med_addr.ss = *(struct sockaddr_storage *)&ssin6;
+ }
+ else {
+ /* either a hostname or a value unrelated to network */
+ if (strlcpy(me->me_key.med_string, $1,
+ sizeof(me->me_key.med_string)) >=
+ sizeof(me->me_key.med_string)) {
+ yyerror("map element too long: %s", $1);
+ free(me);
+ free(m);
+ free($1);
+ YYERROR;
+ }
+ }
+ }
+ free($1);
+
+ TAILQ_INSERT_TAIL(&m->m_contents, me, me_entry);
+ TAILQ_INSERT_TAIL(conf->sc_maps, m, m_entry);
+ $$ = m->m_id;
+ }
+ | '(' {
+ struct map *m;
+
+ if ((m = calloc(1, sizeof(*m))) == NULL)
+ fatal("out of memory");
+
+ m->m_id = last_map_id++;
+ if (m->m_id == INT_MAX) {
+ yyerror("too many maps defined");
+ free(m);
+ YYERROR;
+ }
+ snprintf(m->m_name, STRLEN, "<dynamic(%u)>", m->m_id);
+ m->m_flags |= F_DYNAMIC|F_USED;
+ m->m_type = T_LIST;
+
+ TAILQ_INIT(&m->m_contents);
+ contents = &m->m_contents;
+ map = m;
+
+ } string_list ')' {
+ TAILQ_INSERT_TAIL(conf->sc_maps, map, m_entry);
+ $$ = map->m_id;
+ }
+ | '{' {
+ struct map *m;
+
+ if ((m = calloc(1, sizeof(*m))) == NULL)
+ fatal("out of memory");
+
+ m->m_id = last_map_id++;
+ if (m->m_id == INT_MAX) {
+ yyerror("too many maps defined");
+ free(m);
+ YYERROR;
+ }
+ snprintf(m->m_name, STRLEN, "<dynamic(%u)>", m->m_id);
+ m->m_flags |= F_DYNAMIC|F_USED;
+ m->m_type = T_HASH;
+
+ TAILQ_INIT(&m->m_contents);
+ contents = &m->m_contents;
+ map = m;
+
+ } keyval_list '}' {
+ TAILQ_INSERT_TAIL(conf->sc_maps, map, m_entry);
+ $$ = map->m_id;
+ }
+ | MAP STRING {
+ struct map *m;
+
+ if ((m = map_findbyname(conf, $2)) == NULL) {
+ yyerror("no such map: %s");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ m->m_flags |= F_USED;
+ $$ = m->m_id;
+ }
+ ;
+
+decision : ACCEPT { $$ = 1; }
+ | REJECT { $$ = 0; }
+ ;
+
+condition : NETWORK mapref {
+ struct cond *c;
+
+ if ((c = calloc(1, sizeof *c)) == NULL)
+ fatal("out of memory");
+ c->c_type = C_NET;
+ c->c_map = $2;
+ $$ = c;
+ }
+ | DOMAIN mapref {
+ struct cond *c;
+
+ if ((c = calloc(1, sizeof *c)) == NULL)
+ fatal("out of memory");
+ c->c_type = C_DOM;
+ c->c_map = $2;
+ $$ = c;
+ }
+ | ALL {
+ struct cond *c;
+
+ if ((c = calloc(1, sizeof *c)) == NULL)
+ fatal("out of memory");
+ c->c_type = C_ALL;
+ $$ = c;
+ }
+ ;
+
+condition_list : condition comma condition_list {
+ TAILQ_INSERT_TAIL(&rule->r_conditions, $1, c_entry);
+ }
+ ;
+
+conditions : condition {
+ TAILQ_INSERT_TAIL(&rule->r_conditions, $1, c_entry);
+ }
+ | '{' condition_list '}'
+ ;
+
+action : DELIVER TO MAILDIR STRING {
+ rule->r_action = A_MAILDIR;
+ if (strlcpy(rule->r_value.path, $4, MAXPATHLEN)
+ >= MAXPATHLEN)
+ fatal("pathname too long");
+ free($4);
+ }
+ | DELIVER TO MBOX STRING {
+ rule->r_action = A_MBOX;
+ if (strlcpy(rule->r_value.path, $4, MAXPATHLEN)
+ >= MAXPATHLEN)
+ fatal("pathname too long");
+ free($4);
+ }
+ | DELIVER TO MDA STRING {
+ rule->r_action = A_EXT;
+ if (strlcpy(rule->r_value.command, $4, MAXPATHLEN)
+ >= MAXPATHLEN)
+ fatal("command too long");
+ free($4);
+ }
+ | RELAY {
+ rule->r_action = A_RELAY;
+ }
+ | RELAY VIA STRING PORT NUMBER {
+ rule->r_action = A_RELAYVIA;
+ if (strlcpy(rule->r_value.host.hostname, $3, MAXHOSTNAMELEN)
+ >= MAXHOSTNAMELEN)
+ fatal("hostname too long");
+ if ($5 <= 0 || $5 >= (int)USHRT_MAX) {
+ yyerror("invalid port: %d", $5);
+ YYERROR;
+ }
+ rule->r_value.host.port = $5;
+ free($3);
+ }
+ ;
+
+from : FROM mapref {
+ $$ = $2;
+ }
+ | /* empty */ {
+ struct map *m;
+ struct mapel *me;
+ struct sockaddr_in *ssin;
+ struct sockaddr_in6 *ssin6;
+
+ if ((m = calloc(1, sizeof(*m))) == NULL)
+ fatal("out of memory");
+ m->m_id = last_map_id++;
+ if (m->m_id == INT_MAX) {
+ yyerror("too many maps defined");
+ free(m);
+ YYERROR;
+ }
+ snprintf(m->m_name, STRLEN, "<dynamic(%u)>", m->m_id);
+ m->m_flags |= F_DYNAMIC|F_USED;
+ m->m_type = T_SINGLE;
+
+ TAILQ_INIT(&m->m_contents);
+
+ if ((me = calloc(1, sizeof(*me))) == NULL)
+ fatal("out of memory");
+ me->me_key.med_addr.masked = 0;
+ ssin = (struct sockaddr_in *)&me->me_key.med_addr.ss;
+ ssin->sin_family = AF_INET;
+ if (inet_pton(AF_INET, "127.0.0.1", &ssin->sin_addr) != 1) {
+ free(me);
+ free(m);
+ YYERROR;
+ }
+ TAILQ_INSERT_TAIL(&m->m_contents, me, me_entry);
+
+ if ((me = calloc(1, sizeof(*me))) == NULL)
+ fatal("out of memory");
+ me->me_key.med_addr.masked = 0;
+ ssin6 = (struct sockaddr_in6 *)&me->me_key.med_addr.ss;
+ ssin6->sin6_family = AF_INET6;
+ if (inet_pton(AF_INET6, "::1", &ssin6->sin6_addr) != 1) {
+ free(me);
+ free(m);
+ YYERROR;
+ }
+ TAILQ_INSERT_TAIL(&m->m_contents, me, me_entry);
+
+ TAILQ_INSERT_TAIL(conf->sc_maps, m, m_entry);
+ $$ = m->m_id;
+ }
+ ;
+
+rule : decision from {
+ struct rule *r;
+
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ fatal("out of memory");
+ rule = r;
+ rule->r_sources = map_find(conf, $2);
+ TAILQ_INIT(&rule->r_conditions);
+ TAILQ_INIT(&rule->r_options);
+
+ } FOR conditions action {
+ TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry);
+ }
+ ;
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ file->errors++;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "accept", ACCEPT },
+ { "all", ALL },
+ { "certificate", CERTIFICATE },
+ { "config", CONFIG },
+ { "db", DB },
+ { "deliver", DELIVER },
+ { "dns", DNS },
+ { "domain", DOMAIN },
+ { "external", EXTERNAL },
+ { "file", TFILE },
+ { "for", FOR },
+ { "from", FROM },
+ { "hash", HASH },
+ { "hostname", HOSTNAME },
+ { "include", INCLUDE },
+ { "interval", INTERVAL },
+ { "list", LIST },
+ { "listen", LISTEN },
+ { "maildir", MAILDIR },
+ { "map", MAP },
+ { "mbox", MBOX },
+ { "mda", MDA },
+ { "network", NETWORK },
+ { "on", ON },
+ { "port", PORT },
+ { "queue", QUEUE },
+ { "reject", REJECT },
+ { "relay", RELAY },
+ { "single", SINGLE },
+ { "source", SOURCE },
+ { "ssl", SSL },
+ { "ssmtp", SSMTP },
+ { "to", TO },
+ { "type", TYPE },
+ { "use", USE },
+ { "via", VIA },
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+char *parsebuf;
+int parseindex;
+char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+ pushback_index = 0;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n')
+ continue;
+ else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IRWXG | S_IRWXO)) {
+ log_warnx("%s: group/world readable/writeable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
+ (nfile->name = strdup(name)) == NULL) {
+ log_warn("malloc");
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("%s", nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+int
+parse_config(struct smtpd *x_conf, const char *filename, int opts)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+ bzero(conf, sizeof(*conf));
+ if ((conf->sc_maps = calloc(1, sizeof(*conf->sc_maps))) == NULL ||
+ (conf->sc_rules = calloc(1, sizeof(*conf->sc_rules))) == NULL) {
+ log_warn("cannot allocate memory");
+ return 0;
+ }
+
+ errors = 0;
+ last_map_id = 0;
+
+ map = NULL;
+ rule = NULL;
+
+ TAILQ_INIT(&conf->sc_listeners);
+ TAILQ_INIT(conf->sc_maps);
+ TAILQ_INIT(conf->sc_rules);
+ SPLAY_INIT(&conf->sc_sessions);
+ SPLAY_INIT(&conf->sc_ssl);
+
+ conf->sc_qintval.tv_sec = SMTPD_QUEUE_INTERVAL;
+ conf->sc_qintval.tv_usec = 0;
+ conf->sc_opts = opts;
+
+ if ((file = pushfile(filename, 0)) == NULL) {
+ purge_config(conf, PURGE_EVERYTHING);
+ return (-1);
+ }
+ topfile = file;
+
+ /*
+ * parse configuration
+ */
+ setservent(1);
+ yyparse();
+ errors = file->errors;
+ popfile();
+ endservent();
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if ((conf->sc_opts & SMTPD_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (TAILQ_EMPTY(conf->sc_rules)) {
+ log_warnx("no rules, nothing to do");
+ errors++;
+ }
+
+ if (strlen(conf->sc_hostname) == 0)
+ if (gethostname(conf->sc_hostname,
+ sizeof(conf->sc_hostname)) == -1) {
+ log_warn("could not determine host name");
+ bzero(conf->sc_hostname, sizeof(conf->sc_hostname));
+ errors++;
+ }
+
+ if (errors) {
+ purge_config(conf, PURGE_EVERYTHING);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entry))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ (void)strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct listener *
+host_v4(const char *s, in_port_t port)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct listener *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+ sain->sin_port = port;
+
+ return (h);
+}
+
+struct listener *
+host_v6(const char *s, in_port_t port)
+{
+ struct in6_addr ina6;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ bzero(&ina6, sizeof(ina6));
+ if (inet_pton(AF_INET6, s, &ina6) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = port;
+ memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6));
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct listenerlist *al, int max, in_port_t port,
+ u_int8_t flags)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct listener *h;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("host_dns: could not parse \"%s\": %s", s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ h->port = port;
+ h->flags = flags;
+ h->ss.ss_family = res->ai_family;
+ h->ssl = NULL;
+ (void)strlcpy(h->ssl_cert_name, s, sizeof(h->ssl_cert_name));
+
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ sain->sin_port = port;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_port = port;
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("host_dns: %s resolves to more than %d hosts",
+ s, max);
+ }
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+host(const char *s, struct listenerlist *al, int max, in_port_t port,
+ u_int8_t flags)
+{
+ struct listener *h;
+
+ h = host_v4(s, port);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s, port);
+
+ if (h != NULL) {
+ h->port = port;
+ h->flags = flags;
+ h->ssl = NULL;
+ (void)strlcpy(h->ssl_cert_name, s, sizeof(h->ssl_cert_name));
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ return (1);
+ }
+
+ return (host_dns(s, al, max, port, flags));
+}
diff --git a/usr.sbin/smtpd/queue.c b/usr.sbin/smtpd/queue.c
new file mode 100644
index 00000000000..3938c01bfaf
--- /dev/null
+++ b/usr.sbin/smtpd/queue.c
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void queue_shutdown(void);
+void queue_sig_handler(int, short, void *);
+void queue_dispatch_control(int, short, void *);
+void queue_dispatch_smtp(int, short, void *);
+void queue_dispatch_mda(int, short, void *);
+void queue_dispatch_mta(int, short, void *);
+void queue_dispatch_lka(int, short, void *);
+void queue_setup_events(struct smtpd *);
+void queue_disable_events(struct smtpd *);
+void queue_timeout(int, short, void *);
+int queue_create_message_file(char *);
+void queue_delete_message_file(char *);
+int queue_record_submission(struct message *);
+int queue_remove_submission(struct message *);
+struct batch *batch_lookup(struct smtpd *, struct message *);
+int batch_schedule(struct batch *, time_t);
+void batch_unschedule(struct batch *);
+void batch_send(struct smtpd *, struct batch *, time_t);
+int queue_update_database(struct message *);
+int queue_open_message_file(struct batch *);
+int queue_batch_resolved(struct smtpd *, struct batch *);
+struct batch *queue_record_batch(struct smtpd *, struct message *);
+struct batch *batch_by_id(struct smtpd *, u_int64_t);
+struct message *message_by_id(struct smtpd *, struct batch *, u_int64_t);
+void queue_mailer_daemon(struct smtpd *, struct batch *, enum batch_status);
+void debug_display_batch(struct batch *);
+void debug_display_message(struct message *);
+int queue_record_daemon(struct message *);
+struct batch *queue_register_daemon_batch(struct smtpd *, struct batch *);
+void queue_register_daemon_message(struct smtpd *, struct batch *, struct message *);
+void queue_load_submissions(struct smtpd *, time_t);
+int queue_message_schedule(struct message *, time_t);
+int queue_message_from_id(char *, struct message *);
+
+void
+queue_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ queue_shutdown();
+ break;
+ default:
+ fatalx("queue_sig_handler: unexpected signal");
+ }
+}
+
+void
+queue_dispatch_control(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_CONTROL];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("queue_dispatch_control: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("queue_dispatch_control: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+queue_dispatch_smtp(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_SMTP];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("queue_dispatch_smtp: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_QUEUE_CREATE_MESSAGE_FILE: {
+ struct message *messagep;
+ struct submit_status ss;
+ int fd;
+
+ log_debug("mfa_dispatch_smtp: creating message file");
+ messagep = imsg.data;
+ ss.id = messagep->session_id;
+ ss.code = 250;
+ fd = queue_create_message_file(ss.u.msgid);
+ imsg_compose(ibuf, IMSG_SMTP_MESSAGE_FILE, 0, 0, fd,
+ &ss, sizeof(ss));
+ break;
+ }
+ case IMSG_QUEUE_DELETE_MESSAGE_FILE: {
+ struct message *messagep;
+
+ messagep = imsg.data;
+ queue_delete_message_file(messagep->message_id);
+ break;
+ }
+ case IMSG_QUEUE_MESSAGE_SUBMIT: {
+ struct message *messagep;
+ struct submit_status ss;
+
+ messagep = imsg.data;
+ messagep->id = queue_generate_id();
+ ss.id = messagep->session_id;
+ ss.code = 250;
+ ss.u.path = messagep->recipient;
+
+ if (IS_MAILBOX(messagep->recipient.rule.r_action) ||
+ IS_EXT(messagep->recipient.rule.r_action))
+ messagep->type = T_MDA_MESSAGE;
+ else
+ messagep->type = T_MTA_MESSAGE;
+
+ /* Write to disk */
+ queue_record_submission(messagep);
+ imsg_compose(ibuf, IMSG_SMTP_SUBMIT_ACK, 0, 0, -1,
+ &ss, sizeof(ss));
+
+ if (messagep->type & T_MTA_MESSAGE) {
+ messagep->flags |= F_MESSAGE_READY;
+ queue_update_database(messagep);
+ break;
+ }
+
+ if ((messagep->recipient.flags & (F_ALIAS|F_VIRTUAL)) == 0) {
+ /* not an alias, perform ~/.forward resolution */
+ imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1,
+ messagep, sizeof(struct message));
+ break;
+ }
+
+ /* Recipient is an alias, proceed to resolving it.
+ * ~/.forward will be handled by the IMSG_LKA_ALIAS_RESULT
+ * dispatch case.
+ */
+ imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_ALIAS_LOOKUP, 0, 0, -1,
+ messagep, sizeof (struct message));
+
+ break;
+ }
+ default:
+ log_debug("queue_dispatch_smtp: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+queue_dispatch_mda(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MDA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("queue_dispatch_mda: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+
+ case IMSG_QUEUE_MESSAGE_UPDATE: {
+ struct message *messagep;
+
+ messagep = (struct message *)imsg.data;
+ messagep->batch_id = 0;
+ messagep->retry++;
+
+ if (messagep->status & S_MESSAGE_TEMPFAILURE) {
+ messagep->status &= ~S_MESSAGE_TEMPFAILURE;
+ messagep->flags &= ~F_MESSAGE_PROCESSING;
+ queue_update_database(messagep);
+ break;
+ }
+
+ if (messagep->status & S_MESSAGE_PERMFAILURE) {
+ struct message msave;
+
+ messagep->status &= ~S_MESSAGE_PERMFAILURE;
+ if ((messagep->type & T_DAEMON_MESSAGE) == 0) {
+ msave = *messagep;
+ messagep->id = queue_generate_id();
+ messagep->batch_id = 0;
+ messagep->type |= T_DAEMON_MESSAGE;
+ messagep->flags |= F_MESSAGE_READY;
+ messagep->lasttry = 0;
+ messagep->retry = 0;
+ queue_record_submission(messagep);
+ *messagep = msave;
+ }
+ queue_remove_submission(messagep);
+ break;
+ }
+
+ /* no error, remove submission */
+ queue_remove_submission(messagep);
+ break;
+ }
+
+ default:
+ log_debug("queue_dispatch_mda: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+queue_dispatch_mta(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MTA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("queue_dispatch_mda: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+
+ case IMSG_QUEUE_MESSAGE_FD: {
+ int fd;
+ struct batch *batchp;
+
+ batchp = imsg.data;
+ fd = queue_open_message_file(batchp);
+ imsg_compose(ibuf, IMSG_QUEUE_MESSAGE_FD, 0, 0, fd, batchp,
+ sizeof(*batchp));
+ break;
+ }
+
+ case IMSG_QUEUE_MESSAGE_UPDATE: {
+ struct message *messagep;
+
+ messagep = (struct message *)imsg.data;
+ messagep->batch_id = 0;
+ messagep->retry++;
+
+ if (messagep->status & S_MESSAGE_TEMPFAILURE) {
+ messagep->status &= ~S_MESSAGE_TEMPFAILURE;
+ messagep->flags &= ~F_MESSAGE_PROCESSING;
+ queue_update_database(messagep);
+ break;
+ }
+
+ if (messagep->status & S_MESSAGE_PERMFAILURE) {
+ struct message msave;
+
+ messagep->status &= ~S_MESSAGE_PERMFAILURE;
+ if ((messagep->type & T_DAEMON_MESSAGE) == 0) {
+ msave = *messagep;
+ messagep->id = queue_generate_id();
+ messagep->batch_id = 0;
+ messagep->type |= T_DAEMON_MESSAGE;
+ messagep->flags |= F_MESSAGE_READY;
+ messagep->lasttry = 0;
+ messagep->retry = 0;
+ queue_record_submission(messagep);
+ *messagep = msave;
+ }
+ queue_remove_submission(messagep);
+ break;
+ }
+
+ /* no error, remove submission */
+ queue_remove_submission(messagep);
+ break;
+ }
+
+ default:
+ log_debug("queue_dispatch_mda: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+queue_dispatch_lka(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_LKA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("queue_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+
+ case IMSG_LKA_ALIAS_RESULT: {
+ struct message *messagep;
+
+ messagep = imsg.data;
+ messagep->id = queue_generate_id();
+ messagep->batch_id = 0;
+ queue_record_submission(messagep);
+
+ if (messagep->type & T_MTA_MESSAGE) {
+ messagep->flags |= F_MESSAGE_READY;
+ queue_update_database(messagep);
+ }
+
+ if (messagep->type & T_MDA_MESSAGE) {
+ imsg_compose(ibuf, IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1,
+ messagep, sizeof(struct message));
+ }
+ break;
+ }
+
+ case IMSG_LKA_FORWARD_LOOKUP: {
+ struct message *messagep;
+
+ messagep = (struct message *)imsg.data;
+ messagep->id = queue_generate_id();
+ messagep->batch_id = 0;
+ messagep->flags |= F_MESSAGE_READY;
+ queue_record_submission(messagep);
+ break;
+ }
+
+ case IMSG_QUEUE_REMOVE_SUBMISSION: {
+ struct message *messagep;
+
+ messagep = (struct message *)imsg.data;
+ queue_remove_submission(messagep);
+ break;
+ }
+
+ case IMSG_LKA_MX_LOOKUP: {
+ queue_batch_resolved(env, imsg.data);
+ break;
+ }
+ default:
+ log_debug("queue_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+queue_shutdown(void)
+{
+ log_info("queue handler");
+ _exit(0);
+}
+
+void
+queue_setup_events(struct smtpd *env)
+{
+ struct timeval tv;
+
+ evtimer_set(&env->sc_ev, queue_timeout, env);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+queue_disable_events(struct smtpd *env)
+{
+ evtimer_del(&env->sc_ev);
+}
+
+void
+queue_timeout(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct batch *batchp, *nxt;
+ struct timeval tv;
+ time_t curtime;
+
+ curtime = time(NULL);
+ queue_load_submissions(env, curtime);
+
+ for (batchp = SPLAY_MIN(batchtree, &env->batch_queue);
+ batchp != NULL;
+ batchp = nxt) {
+ nxt = SPLAY_NEXT(batchtree, &env->batch_queue, batchp);
+
+ if ((batchp->type & T_MTA_BATCH) &&
+ (batchp->flags & F_BATCH_RESOLVED) == 0)
+ continue;
+
+ batch_send(env, batchp, curtime);
+
+ SPLAY_REMOVE(batchtree, &env->batch_queue, batchp);
+ bzero(batchp, sizeof(struct batch));
+ free(batchp);
+
+ }
+
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+queue_load_submissions(struct smtpd *env, time_t tm)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ struct batch *batchp;
+ struct message *messagep;
+ struct message message;
+
+ dirp = opendir(PATH_ENVELOPES);
+ if (dirp == NULL)
+ err(1, "opendir");
+
+ while ((dp = readdir(dirp)) != NULL) {
+
+ if (dp->d_name[0] == '.')
+ continue;
+
+ if (! queue_message_from_id(dp->d_name, &message))
+ errx(1, "failed to load message");
+
+ if (! queue_message_schedule(&message, tm)) {
+ if (message.flags & F_MESSAGE_EXPIRED) {
+ log_debug("message expired, create mdaemon");
+ queue_remove_submission(&message);
+ }
+ continue;
+ }
+ message.lasttry = tm;
+ message.flags |= F_MESSAGE_PROCESSING;
+ queue_update_database(&message);
+
+ messagep = calloc(1, sizeof (struct message));
+ if (messagep == NULL)
+ err(1, "calloc");
+ *messagep = message;
+
+ batchp = batch_lookup(env, messagep);
+ if (batchp != NULL)
+ messagep->batch_id = batchp->id;
+
+ batchp = queue_record_batch(env, messagep);
+ if (messagep->batch_id == 0)
+ messagep->batch_id = batchp->id;
+
+ }
+
+ closedir(dirp);
+}
+
+int
+queue_message_from_id(char *message_id, struct message *message)
+{
+ char pathname[MAXPATHLEN];
+ int fd;
+ int ret;
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_ENVELOPES, message_id)
+ >= MAXPATHLEN) {
+ warnx("queue_load_submissions: filename too long.");
+ return 0;
+ }
+
+ fd = open(pathname, O_RDONLY);
+ if (fd == -1) {
+ warnx("queue_load_submissions: open: %s", message_id);
+ goto bad;
+ }
+
+ ret = atomic_read(fd, message, sizeof(struct message));
+ if (ret != sizeof(struct message)) {
+ warnx("queue_load_submissions: atomic_read: %s", message_id);
+ goto bad;
+ }
+
+ close(fd);
+ return 1;
+bad:
+ if (fd != -1)
+ close(fd);
+ return 0;
+}
+
+pid_t
+queue(struct smtpd *env)
+{
+ pid_t pid;
+ struct passwd *pw;
+
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_CONTROL, queue_dispatch_control },
+ { PROC_SMTP, queue_dispatch_smtp },
+ { PROC_MDA, queue_dispatch_mda },
+ { PROC_MTA, queue_dispatch_mta },
+ { PROC_LKA, queue_dispatch_lka }
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("queue: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+#ifndef DEBUG
+ if (chroot(PATH_SPOOL) == -1)
+ fatal("queue: chroot");
+ if (chdir("/") == -1)
+ fatal("queue: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("queue handler");
+ smtpd_process = PROC_QUEUE;
+
+#ifndef DEBUG
+ 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("queue: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, queue_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, queue_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 5);
+
+ SPLAY_INIT(&env->batch_queue);
+
+ queue_setup_events(env);
+ event_dispatch();
+ queue_shutdown();
+
+ return (0);
+}
+
+u_int64_t
+queue_generate_id(void)
+{
+ u_int64_t id;
+ struct timeval tp;
+
+ if (gettimeofday(&tp, NULL) == -1)
+ fatal("queue_generate_id: time");
+
+ id = (u_int32_t)tp.tv_sec;
+ id <<= 32;
+ id |= (u_int32_t)tp.tv_usec;
+ usleep(1);
+
+ return (id);
+}
+
+int
+queue_create_message_file(char *message_id)
+{
+ int fd;
+ char pathname[MAXPATHLEN];
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%d.XXXXXXXXXXXXXXXX",
+ PATH_MESSAGES, time(NULL)) >= MAXPATHLEN)
+ return -1;
+
+ fd = mkstemp(pathname);
+ if (fd == -1)
+ fatal("queue_create_message_file: mkstemp");
+
+ /* XXX - this won't fail if message_id is MAXPATHLEN bytes */
+ if (strlcpy(message_id, pathname + sizeof(PATH_MESSAGES), MAXPATHLEN)
+ >= MAXPATHLEN)
+ fatal("queue_create_message_file: message id too long");
+
+ return fd;
+}
+
+void
+queue_delete_message_file(char *message_id)
+{
+ char pathname[MAXPATHLEN];
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_MESSAGES, message_id)
+ >= MAXPATHLEN)
+ fatal("queue_delete_message_file: message id too long");
+
+ if (unlink(pathname) == -1)
+ fatal("queue_delete_message_file: unlink");
+}
+
+int
+queue_record_submission(struct message *message)
+{
+ char pathname[MAXPATHLEN];
+ char linkname[MAXPATHLEN];
+ char dbname[MAXPATHLEN];
+ char message_uid[MAXPATHLEN];
+ char *spool;
+ size_t spoolsz;
+ int fd;
+ int mode = O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_SYNC|O_EXLOCK;
+
+ if (message->type & T_DAEMON_MESSAGE) {
+ spool = PATH_DAEMON;
+ }
+ else {
+ switch (message->recipient.rule.r_action) {
+ case A_MBOX:
+ case A_MAILDIR:
+ case A_EXT:
+ spool = PATH_LOCAL;
+ break;
+ default:
+ spool = PATH_RELAY;
+ }
+ }
+ spoolsz = strlen(spool);
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_MESSAGES,
+ message->message_id) >= MAXPATHLEN)
+ fatal("queue_record_submission: message id too long");
+
+ for (;;) {
+ if (snprintf(linkname, MAXPATHLEN, "%s/%s.%qu", spool,
+ message->message_id, (u_int64_t)arc4random())
+ >= MAXPATHLEN)
+ fatal("queue_record_submission: message uid too long");
+
+ (void)strlcpy(message_uid, linkname + spoolsz + 1, MAXPATHLEN);
+
+ if (link(pathname, linkname) == -1) {
+ if (errno == EEXIST)
+ continue;
+ err(1, "link: %s , %s", pathname, linkname);
+ }
+
+ if (snprintf(dbname, MAXPATHLEN, "%s/%s", PATH_ENVELOPES,
+ message_uid) >= MAXPATHLEN)
+ fatal("queue_record_submission: database uid too long");
+
+ fd = open(dbname, mode, 0600);
+ if (fd == -1)
+ if (unlink(linkname) == -1)
+ fatal("queue_record_submission: unlink");
+
+ if (strlcpy(message->message_uid, message_uid, MAXPATHLEN)
+ >= MAXPATHLEN)
+ fatal("queue_record_submission: message uid too long");
+
+ message->creation = time(NULL);
+
+ if (atomic_write(fd, message, sizeof(struct message))
+ != sizeof(struct message)) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ break;
+ }
+ return 1;
+}
+
+struct batch *
+queue_record_batch(struct smtpd *env, struct message *messagep)
+{
+ struct batch *batchp;
+ struct path *path;
+
+ batchp = NULL;
+ if (messagep->batch_id != 0) {
+ batchp = batch_by_id(env, messagep->batch_id);
+ if (batchp == NULL)
+ errx(1, "%s: internal inconsistency.", __func__);
+ }
+
+ if (batchp == NULL) {
+ batchp = calloc(1, sizeof(struct batch));
+ if (batchp == NULL)
+ err(1, "%s: calloc", __func__);
+
+ batchp->id = queue_generate_id();
+ batchp->creation = messagep->creation;
+
+ (void)strlcpy(batchp->message_id, messagep->message_id,
+ sizeof(batchp->message_id));
+
+ TAILQ_INIT(&batchp->messages);
+ SPLAY_INSERT(batchtree, &env->batch_queue, batchp);
+
+ if (messagep->type & T_DAEMON_MESSAGE) {
+ batchp->type = T_DAEMON_BATCH;
+ path = &messagep->sender;
+ }
+ else {
+ path = &messagep->recipient;
+ }
+
+ batchp->rule = path->rule;
+
+ (void)strlcpy(batchp->hostname, path->domain,
+ sizeof(batchp->hostname));
+
+ if (IS_MAILBOX(path->rule.r_action) ||
+ IS_EXT(path->rule.r_action)) {
+ batchp->type |= T_MDA_BATCH;
+ }
+ else {
+ batchp->type |= T_MTA_BATCH;
+ imsg_compose(env->sc_ibufs[PROC_LKA], IMSG_LKA_MX_LOOKUP, 0, 0, -1,
+ batchp, sizeof(struct batch));
+ }
+ }
+
+ TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry);
+
+ return batchp;
+}
+
+int
+queue_remove_submission(struct message *message)
+{
+ char pathname[MAXPATHLEN];
+ char linkname[MAXPATHLEN];
+ char dbname[MAXPATHLEN];
+ char *spool;
+ struct stat sb;
+
+ if (message->type & T_DAEMON_MESSAGE) {
+ spool = PATH_DAEMON;
+ }
+ else {
+ switch (message->recipient.rule.r_action) {
+ case A_MBOX:
+ case A_MAILDIR:
+ case A_EXT:
+ spool = PATH_LOCAL;
+ break;
+ default:
+ spool = PATH_RELAY;
+ }
+ }
+
+ if (snprintf(dbname, MAXPATHLEN, "%s/%s", PATH_ENVELOPES,
+ message->message_uid) >= MAXPATHLEN)
+ fatal("queue_remove_submission: database uid too long");
+
+ if (snprintf(linkname, MAXPATHLEN, "%s/%s", spool,
+ message->message_uid) >= MAXPATHLEN)
+ fatal("queue_remove_submission: message uid too long");
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_MESSAGES,
+ message->message_id) >= MAXPATHLEN)
+ fatal("queue_remove_submission: message id too long");
+
+ if (unlink(dbname) == -1) {
+ warnx("dbname: %s", dbname);
+ fatal("queue_remove_submission: unlink");
+ }
+
+ if (unlink(linkname) == -1) {
+ warnx("linkname: %s", linkname);
+ fatal("queue_remove_submission: unlink");
+ }
+
+ if (stat(pathname, &sb) == -1) {
+ warnx("pathname: %s", pathname);
+ fatal("queue_remove_submission: stat");
+ }
+
+ if (sb.st_nlink == 1) {
+ if (unlink(pathname) == -1) {
+ warnx("pathname: %s", pathname);
+ fatal("queue_remove_submission: unlink");
+ }
+ }
+
+ return 1;
+}
+
+int
+queue_remove_batch_message(struct smtpd *env, struct batch *batchp, struct message *messagep)
+{
+ TAILQ_REMOVE(&batchp->messages, messagep, entry);
+ bzero(messagep, sizeof(struct message));
+ free(messagep);
+
+ if (TAILQ_FIRST(&batchp->messages) == NULL) {
+ SPLAY_REMOVE(batchtree, &env->batch_queue, batchp);
+ bzero(batchp, sizeof(struct batch));
+ free(batchp);
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+queue_batch_resolved(struct smtpd *env, struct batch *lookup)
+{
+ u_int32_t i;
+ struct batch *batchp;
+
+ batchp = batch_by_id(env, lookup->id);
+ batchp->h_errno = lookup->h_errno;
+ batchp->ss_cnt = lookup->ss_cnt;
+
+/*
+ EAI_NODATA no address associated with hostname
+ EAI_NONAME hostname or servname not provided, or not known
+ EAI_PROTOCOL resolved protocol is unknown
+ EAI_SERVICE servname not supported for ai_socktype
+ EAI_SOCKTYPE ai_socktype not supported
+ EAI_SYSTEM system error returned in errno
+
+
+ */
+
+ switch (batchp->h_errno) {
+ case EAI_ADDRFAMILY:
+ case EAI_BADFLAGS:
+ case EAI_BADHINTS:
+ case EAI_FAIL:
+ case EAI_FAMILY:
+ case EAI_NODATA:
+ case EAI_NONAME:
+ case EAI_SERVICE:
+ case EAI_SOCKTYPE:
+ case EAI_SYSTEM:
+ /* XXX */
+ /*
+ * In the case of a DNS permanent error, do not generate a
+ * daemon message if the error originates from one already
+ * as this would cause a loop. Remove the initial batch as
+ * it will never succeed.
+ *
+ */
+ return 0;
+
+ case EAI_AGAIN:
+ case EAI_MEMORY:
+ /* XXX */
+ /*
+ * Do not generate a daemon message if this error happened
+ * while processing a daemon message. Do NOT remove batch,
+ * it may succeed later.
+ */
+ return 0;
+
+ default:
+ batchp->flags |= F_BATCH_RESOLVED;
+ for (i = 0; i < batchp->ss_cnt; ++i)
+ batchp->ss[i] = lookup->ss[i];
+ }
+ return 1;
+}
+
+int
+queue_open_message_file(struct batch *batch)
+{
+ int fd;
+ char pathname[MAXPATHLEN];
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_MESSAGES,
+ batch->message_id) >= MAXPATHLEN)
+ fatal("queue_open_message_file: message id too long");
+
+ fd = open(pathname, O_RDONLY);
+ if (fd == -1)
+ fatal("queue_open_message_file: open");
+
+ return fd;
+}
+
+int
+queue_update_database(struct message *message)
+{
+ int fd;
+ char *spool;
+ char pathname[MAXPATHLEN];
+
+ if (message->type & T_DAEMON_MESSAGE) {
+ spool = PATH_DAEMON;
+ }
+ else {
+ switch (message->recipient.rule.r_action) {
+ case A_MBOX:
+ case A_MAILDIR:
+ case A_EXT:
+ spool = PATH_LOCAL;
+ break;
+ default:
+ spool = PATH_RELAY;
+ }
+ }
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/%s", PATH_ENVELOPES,
+ message->message_uid) >= MAXPATHLEN)
+ fatal("queue_update_database: pathname too long");
+
+ if ((fd = open(pathname, O_RDWR|O_EXLOCK)) == -1)
+ fatal("queue_update_database: cannot open database");
+
+ if (atomic_write(fd, message, sizeof (struct message)) == -1)
+ fatal("queue_update_database: cannot open database");
+
+ close(fd);
+
+ return 1;
+}
+
+
+int
+queue_record_daemon(struct message *message)
+{
+ char pathname[MAXPATHLEN];
+ char linkname[MAXPATHLEN];
+ char dbname[MAXPATHLEN];
+ char message_uid[MAXPATHLEN];
+ size_t spoolsz;
+ int fd;
+ int mode = O_CREAT|O_TRUNC|O_WRONLY|O_EXCL|O_SYNC|O_EXLOCK;
+
+ (void)snprintf(pathname, MAXPATHLEN, "%s/%s",
+ PATH_MESSAGES, message->message_id);
+
+ spoolsz = strlen(PATH_DAEMON);
+
+ for (;;) {
+ (void)snprintf(linkname, MAXPATHLEN, "%s/%s.%qu",
+ PATH_DAEMON, message->message_id, (u_int64_t)arc4random());
+ (void)strlcpy(message_uid, linkname + spoolsz + 1, MAXPATHLEN);
+
+ if (link(pathname, linkname) == -1) {
+ if (errno == EEXIST)
+ continue;
+ err(1, "link");
+ }
+
+ (void)snprintf(dbname, MAXPATHLEN, "%s/%s",
+ PATH_ENVELOPES, message_uid);
+
+ fd = open(dbname, mode, 0600);
+ if (fd == -1)
+ if (unlink(linkname) == -1)
+ err(1, "unlink");
+
+ (void)strlcpy(message->message_uid, message_uid, MAXPATHLEN);
+
+ message->creation = time(NULL);
+
+ atomic_write(fd, message, sizeof(*message));
+ close(fd);
+ break;
+ }
+
+ return 1;
+}
+
+
+struct batch *
+batch_lookup(struct smtpd *env, struct message *message)
+{
+ struct batch *batchp;
+ struct batch lookup;
+
+ /* If message->batch_id != 0, we can retrieve batch by id */
+ if (message->batch_id != 0) {
+ lookup.id = message->batch_id;
+ return SPLAY_FIND(batchtree, &env->batch_queue, &lookup);
+ }
+
+ /* We do not know the batch_id yet, maybe it was created but we could not
+ * be notified, or it just does not exist. Let's scan to see if we can do
+ * a match based on our message_id and flags.
+ */
+ SPLAY_FOREACH(batchp, batchtree, &env->batch_queue) {
+
+ if (batchp->type != message->type)
+ continue;
+
+ if (strcasecmp(batchp->message_id, message->message_id) != 0)
+ continue;
+
+ if (batchp->type & T_MTA_BATCH)
+ if (strcasecmp(batchp->hostname, message->recipient.domain) != 0)
+ continue;
+
+ break;
+ }
+
+ return batchp;
+}
+
+int
+batch_cmp(struct batch *s1, struct batch *s2)
+{
+ /*
+ * do not return u_int64_t's
+ */
+ if (s1->id < s2->id)
+ return (-1);
+
+ if (s1->id > s2->id)
+ return (1);
+
+ return (0);
+}
+
+int
+queue_message_schedule(struct message *messagep, time_t tm)
+{
+ time_t delay;
+
+ /* Batch has been in the queue for too long and expired */
+ if (tm - messagep->creation >= SMTPD_QUEUE_EXPIRY) {
+ messagep->flags |= F_MESSAGE_EXPIRED;
+ return 0;
+ }
+
+ if (messagep->retry == 255) {
+ messagep->flags |= F_MESSAGE_EXPIRED;
+ return 0;
+ }
+
+ if ((messagep->flags & F_MESSAGE_READY) == 0)
+ return 0;
+
+ if ((messagep->flags & F_MESSAGE_PROCESSING) != 0)
+ return 0;
+
+ if (messagep->lasttry == 0)
+ return 1;
+
+ delay = SMTPD_QUEUE_MAXINTERVAL;
+
+ if (messagep->type & T_MDA_MESSAGE) {
+ if (messagep->retry < 5)
+ return 1;
+
+ if (messagep->retry < 15)
+ delay = (messagep->retry * 60) + arc4random() % 60;
+ }
+
+ if (messagep->type & T_MTA_MESSAGE) {
+ if (messagep->retry < 3)
+ delay = SMTPD_QUEUE_INTERVAL;
+ else if (messagep->retry <= 7) {
+ delay = SMTPD_QUEUE_INTERVAL * (1 << (messagep->retry - 3));
+ if (delay > SMTPD_QUEUE_MAXINTERVAL)
+ delay = SMTPD_QUEUE_MAXINTERVAL;
+ }
+ }
+
+ if (tm >= messagep->lasttry + delay)
+ return 1;
+
+ return 0;
+}
+
+void
+batch_unschedule(struct batch *batchp)
+{
+ batchp->flags &= ~(F_BATCH_SCHEDULED);
+}
+
+void
+batch_send(struct smtpd *env, struct batch *batchp, time_t curtime)
+{
+ u_int8_t proctype;
+ struct message *messagep;
+
+ if ((batchp->type & (T_MDA_BATCH|T_MTA_BATCH)) == 0)
+ fatal("batch_send: unknown batch type");
+
+ if (batchp->type & T_MDA_BATCH)
+ proctype = PROC_MDA;
+ else if (batchp->type & T_MTA_BATCH)
+ proctype = PROC_MTA;
+
+ imsg_compose(env->sc_ibufs[proctype], IMSG_CREATE_BATCH, 0, 0, -1,
+ batchp, sizeof (struct batch));
+
+ while ((messagep = TAILQ_FIRST(&batchp->messages))) {
+ imsg_compose(env->sc_ibufs[proctype], IMSG_BATCH_APPEND, 0, 0,
+ -1, messagep, sizeof (struct message));
+ TAILQ_REMOVE(&batchp->messages, messagep, entry);
+ bzero(messagep, sizeof(struct message));
+ free(messagep);
+ }
+
+ imsg_compose(env->sc_ibufs[proctype], IMSG_BATCH_CLOSE, 0, 0, -1,
+ batchp, sizeof(struct batch));
+}
+
+struct batch *
+batch_by_id(struct smtpd *env, u_int64_t id)
+{
+ struct batch lookup;
+
+ lookup.id = id;
+ return SPLAY_FIND(batchtree, &env->batch_queue, &lookup);
+}
+
+struct message *
+message_by_id(struct smtpd *env, struct batch *batchp, u_int64_t id)
+{
+ struct message *messagep;
+
+ if (batchp != NULL) {
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ if (messagep->id == id)
+ break;
+ }
+ return messagep;
+ }
+
+ SPLAY_FOREACH(batchp, batchtree, &env->batch_queue) {
+ TAILQ_FOREACH(messagep, &batchp->messages, entry) {
+ if (messagep->id == id)
+ return messagep;
+ }
+ }
+ return NULL;
+}
+
+SPLAY_GENERATE(batchtree, batch, b_nodes, batch_cmp);
diff --git a/usr.sbin/smtpd/smtp.c b/usr.sbin/smtpd/smtp.c
new file mode 100644
index 00000000000..01f06953648
--- /dev/null
+++ b/usr.sbin/smtpd/smtp.c
@@ -0,0 +1,586 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void smtp_shutdown(void);
+void smtp_sig_handler(int, short, void *);
+void smtp_dispatch_parent(int, short, void *);
+void smtp_dispatch_mfa(int, short, void *);
+void smtp_dispatch_lka(int, short, void *);
+void smtp_dispatch_queue(int, short, void *);
+void smtp_setup_events(struct smtpd *);
+void smtp_disable_events(struct smtpd *);
+void smtp_accept(int, short, void *);
+void session_timeout(int, short, void *);
+
+void
+smtp_sig_handler(int sig, short event, void *p)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ smtp_shutdown();
+ break;
+ default:
+ fatalx("smtp_sig_handler: unexpected signal");
+ }
+}
+
+void
+smtp_dispatch_parent(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_PARENT];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_smtp: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CONF_START:
+ if (env->sc_flags & SMTPD_CONFIGURING)
+ break;
+ env->sc_flags |= SMTPD_CONFIGURING;
+ smtp_disable_events(env);
+ break;
+ case IMSG_CONF_SSL: {
+ struct ssl *s;
+ struct ssl *x_ssl;
+
+ if (!(env->sc_flags & SMTPD_CONFIGURING))
+ break;
+
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+ x_ssl = imsg.data;
+ (void)strlcpy(s->ssl_name, x_ssl->ssl_name,
+ sizeof(s->ssl_name));
+ s->ssl_cert_len = x_ssl->ssl_cert_len;
+ if ((s->ssl_cert = malloc(s->ssl_cert_len + 1)) == NULL)
+ fatal(NULL);
+ (void)strlcpy(s->ssl_cert,
+ (char *)imsg.data + sizeof(*s),
+ s->ssl_cert_len);
+
+ s->ssl_key_len = x_ssl->ssl_key_len;
+ if ((s->ssl_key = malloc(s->ssl_key_len + 1)) == NULL)
+ fatal(NULL);
+ (void)strlcpy(s->ssl_key,
+ (char *)imsg.data + (sizeof(*s) + s->ssl_cert_len),
+ s->ssl_key_len);
+
+ SPLAY_INSERT(ssltree, &env->sc_ssl, s);
+ break;
+ }
+ case IMSG_CONF_LISTENER: {
+ struct listener *l;
+ struct ssl key;
+
+ if (!(env->sc_flags & SMTPD_CONFIGURING))
+ break;
+
+ if ((l = calloc(1, sizeof(*l))) == NULL)
+ fatal(NULL);
+ memcpy(l, imsg.data, sizeof(*l));
+ if ((l->fd = imsg_get_fd(ibuf, &imsg)) == -1)
+ fatal("cannot get fd");
+
+ log_debug("smtp_dispatch_parent: "
+ "got fd %d for listener: %p", l->fd, l);
+
+ (void)strlcpy(key.ssl_name, l->ssl_cert_name,
+ sizeof(key.ssl_name));
+
+ if (l->flags & F_SSL)
+ if ((l->ssl = SPLAY_FIND(ssltree,
+ &env->sc_ssl, &key)) == NULL)
+ fatal("parent and smtp desynchronized");
+
+ TAILQ_INSERT_TAIL(&env->sc_listeners, l, entry);
+ break;
+ }
+ case IMSG_CONF_END:
+ if (!(env->sc_flags & SMTPD_CONFIGURING))
+ break;
+ smtp_setup_events(env);
+ env->sc_flags &= ~SMTPD_CONFIGURING;
+ break;
+ case IMSG_PARENT_AUTHENTICATE: {
+ struct session *s;
+ struct session key;
+ struct session_auth_reply *reply;
+
+ log_debug("smtp_dispatch_parent: parent handled authentication");
+ reply = imsg.data;
+ key.s_id = reply->session_id;
+ key.s_msg.id = reply->session_id;
+
+ s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key);
+ if (s == NULL) {
+ /* Session was removed while we were waiting for the message */
+ break;
+ }
+
+ if (reply->value)
+ s->s_flags |= F_AUTHENTICATED;
+
+ session_pickup(s, NULL);
+
+ break;
+ }
+ default:
+ log_debug("parent_dispatch_smtp: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+smtp_dispatch_mfa(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MFA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("smtp_dispatch_mfa: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_MFA_RCPT_SUBMIT:
+ case IMSG_MFA_RPATH_SUBMIT: {
+ struct submit_status *ss;
+ struct session *s;
+ struct session key;
+
+ log_debug("smtp_dispatch_mfa: mfa handled return path");
+ ss = imsg.data;
+ key.s_id = ss->id;
+ key.s_msg.id = ss->id;
+
+ s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key);
+ if (s == NULL) {
+ /* Session was removed while we were waiting for the message */
+ break;
+ }
+
+ session_pickup(s, ss);
+ break;
+ }
+ default:
+ log_debug("smtp_dispatch_mfa: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+smtp_dispatch_lka(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_LKA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("smtp_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_SMTP_HOSTNAME_ANSWER: {
+ struct session key;
+ struct session *s;
+ struct session *ss;
+
+ s = imsg.data;
+ key.s_id = s->s_id;
+
+ ss = SPLAY_FIND(sessiontree, &env->sc_sessions, &key);
+ if (ss == NULL) {
+ /* Session was removed while we were waiting for the message */
+ break;
+ }
+
+ strlcpy(ss->s_hostname, s->s_hostname, MAXHOSTNAMELEN);
+
+ break;
+ }
+ default:
+ log_debug("smtp_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+smtp_dispatch_queue(int sig, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_QUEUE];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("smtp_dispatch_queue: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_SMTP_MESSAGE_FILE: {
+ struct submit_status *ss;
+ struct session *s;
+ struct session key;
+ int fd;
+
+ log_debug("smtp_dispatch_queue: queue handled message creation");
+ ss = imsg.data;
+
+ key.s_id = ss->id;
+
+ s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key);
+ if (s == NULL) {
+ /* Session was removed while we were waiting for the message */
+ break;
+ }
+
+ (void)strlcpy(s->s_msg.message_id, ss->u.msgid,
+ sizeof(s->s_msg.message_id));
+
+ fd = imsg_get_fd(ibuf, &imsg);
+ if (fd != -1) {
+ s->s_msg.datafp = fdopen(fd, "w");
+ if (s->s_msg.datafp == NULL) {
+ /* no need to handle error, it will be
+ * caught in session_pickup()
+ */
+ close(fd);
+ }
+ }
+ session_pickup(s, ss);
+
+ break;
+ }
+ case IMSG_SMTP_SUBMIT_ACK: {
+ struct submit_status *ss;
+ struct session *s;
+ struct session key;
+
+ log_debug("smtp_dispatch_queue: queue acknowledged message submission");
+ ss = imsg.data;
+ key.s_id = ss->id;
+ key.s_msg.id = ss->id;
+
+ s = SPLAY_FIND(sessiontree, &env->sc_sessions, &key);
+ if (s == NULL) {
+ /* Session was removed while we were waiting for the message */
+ break;
+ }
+
+ (void)strlcpy(s->s_msg.message_id, ss->u.msgid,
+ sizeof(s->s_msg.message_id));
+
+ session_pickup(s, ss);
+
+ break;
+ }
+ default:
+ log_debug("smtp_dispatch_queue: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+smtp_shutdown(void)
+{
+ log_info("smtp server exiting");
+ _exit(0);
+}
+
+pid_t
+smtp(struct smtpd *env)
+{
+ pid_t pid;
+ struct passwd *pw;
+
+ struct event ev_sigint;
+ struct event ev_sigterm;
+
+ struct peer peers[] = {
+ { PROC_PARENT, smtp_dispatch_parent },
+ { PROC_MFA, smtp_dispatch_mfa },
+ { PROC_QUEUE, smtp_dispatch_queue },
+ { PROC_LKA, smtp_dispatch_lka }
+ };
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("smtp: cannot fork");
+ case 0:
+ break;
+ default:
+ return (pid);
+ }
+
+ ssl_init();
+ purge_config(env, PURGE_EVERYTHING);
+
+ pw = env->sc_pw;
+
+#ifndef DEBUG
+ if (chroot(pw->pw_dir) == -1)
+ fatal("smtp: chroot");
+ if (chdir("/") == -1)
+ fatal("smtp: chdir(\"/\")");
+#else
+#warning disabling privilege revocation and chroot in DEBUG MODE
+#endif
+
+ setproctitle("smtp server");
+ smtpd_process = PROC_SMTP;
+
+#ifndef DEBUG
+ 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("smtp: cannot drop privileges");
+#endif
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, smtp_sig_handler, env);
+ signal_set(&ev_sigterm, SIGTERM, smtp_sig_handler, env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+
+ config_peers(env, peers, 4);
+
+ smtp_setup_events(env);
+ event_dispatch();
+ smtp_shutdown();
+
+ return (0);
+}
+
+void
+smtp_setup_events(struct smtpd *env)
+{
+ struct listener *l;
+ struct timeval tv;
+
+ TAILQ_FOREACH(l, &env->sc_listeners, entry) {
+ log_debug("smtp_setup_events: configuring listener: %p%s.",
+ l, (l->flags & F_SSL)?" (with ssl)":"");
+
+ if (fcntl(l->fd, F_SETFL, O_NONBLOCK) == -1)
+ fatal("fcntl");
+ if (listen(l->fd, l->backlog) == -1)
+ fatal("listen");
+ l->env = env;
+ event_set(&l->ev, l->fd, EV_READ, smtp_accept, l);
+ event_add(&l->ev, NULL);
+ ssl_setup(env, l);
+ }
+
+ evtimer_set(&env->sc_ev, session_timeout, env);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+void
+smtp_disable_events(struct smtpd *env)
+{
+ struct listener *l;
+
+ log_debug("smtp_disable_events: closing listening sockets");
+ while ((l = TAILQ_FIRST(&env->sc_listeners)) != NULL) {
+ TAILQ_REMOVE(&env->sc_listeners, l, entry);
+ event_del(&l->ev);
+ close(l->fd);
+ free(l);
+ }
+ TAILQ_INIT(&env->sc_listeners);
+}
+
+void
+smtp_accept(int fd, short event, void *p)
+{
+ int s_fd;
+ struct sockaddr_storage ss;
+ struct listener *l = p;
+ struct session *s;
+ socklen_t len;
+
+ log_debug("smtp_accept: incoming client on listener: %p", l);
+ len = sizeof(struct sockaddr_storage);
+ if ((s_fd = accept(l->fd, (struct sockaddr *)&ss, &len)) == -1) {
+ event_add(&l->ev, NULL);
+ return;
+ }
+
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+ len = sizeof(s->s_ss);
+
+ s->s_fd = s_fd;
+ s->s_tm = time(NULL);
+ (void)memcpy(&s->s_ss, &ss, sizeof(s->s_ss));
+
+ session_init(l, s);
+ event_add(&l->ev, NULL);
+}
+
+void
+smtp_listener_setup(struct smtpd *env, struct listener *l)
+{
+ int opt;
+
+ if ((l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0)) == -1)
+ fatal("socket");
+
+ opt = 1;
+ setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+ if (bind(l->fd, (struct sockaddr *)&l->ss, l->ss.ss_len) == -1)
+ fatal("bind");
+}
diff --git a/usr.sbin/smtpd/smtp_session.c b/usr.sbin/smtpd/smtp_session.c
new file mode 100644
index 00000000000..cfca2f0a99e
--- /dev/null
+++ b/usr.sbin/smtpd/smtp_session.c
@@ -0,0 +1,955 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+int session_rfc5321_helo_handler(struct session *, char *);
+int session_rfc5321_ehlo_handler(struct session *, char *);
+int session_rfc5321_rset_handler(struct session *, char *);
+int session_rfc5321_noop_handler(struct session *, char *);
+int session_rfc5321_data_handler(struct session *, char *);
+int session_rfc5321_mail_handler(struct session *, char *);
+int session_rfc5321_rcpt_handler(struct session *, char *);
+int session_rfc5321_vrfy_handler(struct session *, char *);
+int session_rfc5321_expn_handler(struct session *, char *);
+int session_rfc5321_turn_handler(struct session *, char *);
+int session_rfc5321_help_handler(struct session *, char *);
+int session_rfc5321_quit_handler(struct session *, char *);
+int session_rfc5321_none_handler(struct session *, char *);
+
+int session_rfc1652_mail_handler(struct session *, char *);
+
+int session_rfc3207_stls_handler(struct session *, char *);
+
+int session_rfc4954_auth_handler(struct session *, char *);
+
+void session_command(struct session *, char *, char *);
+int session_set_path(struct path *, char *);
+void session_timeout(int, short, void *);
+
+struct session_timeout {
+ enum session_state state;
+ time_t timeout;
+};
+
+struct session_timeout rfc5321_timeouttab[] = {
+ { S_INIT, 300 },
+ { S_GREETED, 300 },
+ { S_HELO, 300 },
+ { S_MAIL, 300 },
+ { S_RCPT, 300 },
+ { S_DATA, 120 },
+ { S_DATACONTENT, 180 },
+ { S_DONE, 600 }
+};
+
+struct session_cmd {
+ char *name;
+ int (*func)(struct session *, char *);
+};
+
+struct session_cmd rfc5321_cmdtab[] = {
+ { "helo", session_rfc5321_helo_handler },
+ { "ehlo", session_rfc5321_ehlo_handler },
+ { "rset", session_rfc5321_rset_handler },
+ { "noop", session_rfc5321_noop_handler },
+ { "data", session_rfc5321_data_handler },
+ { "mail from", session_rfc5321_mail_handler },
+ { "rcpt to", session_rfc5321_rcpt_handler },
+ { "vrfy", session_rfc5321_vrfy_handler },
+ { "expn", session_rfc5321_expn_handler },
+ { "turn", session_rfc5321_turn_handler },
+ { "help", session_rfc5321_help_handler },
+ { "quit", session_rfc5321_quit_handler }
+};
+
+struct session_cmd rfc1652_cmdtab[] = {
+ { "mail from", session_rfc1652_mail_handler },
+};
+
+struct session_cmd rfc3207_cmdtab[] = {
+ { "starttls", session_rfc3207_stls_handler }
+};
+
+struct session_cmd rfc4954_cmdtab[] = {
+ { "auth", session_rfc4954_auth_handler }
+};
+
+int
+session_rfc3207_stls_handler(struct session *s, char *args)
+{
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ if (args != NULL) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Syntax error (no parameters allowed)\r\n");
+ return 1;
+ }
+
+ evbuffer_add_printf(s->s_bev->output,
+ "220 Ready to start TLS\r\n");
+
+ s->s_state = S_TLS;
+
+ return 1;
+}
+
+int
+session_rfc4954_auth_handler(struct session *s, char *args)
+{
+ char *method;
+ char *eom;
+ struct session_auth_req req;
+
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ if (args == NULL) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Syntax error (no parameters given)\r\n");
+ return 1;
+ }
+
+ method = args;
+ eom = strchr(args, ' ');
+ if (eom == NULL)
+ eom = strchr(args, '\t');
+ if (eom != NULL)
+ *eom++ = '\0';
+
+ if (eom == NULL) {
+ /* NEEDS_FIX - unsupported yet */
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Syntax error\r\n");
+ return 1;
+ }
+
+ req.session_id = s->s_id;
+ if (strlcpy(req.buffer, eom, sizeof(req.buffer)) >=
+ sizeof(req.buffer)) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Syntax error\r\n");
+ return 1;
+ }
+
+ s->s_state = S_AUTH;
+
+ imsg_compose(s->s_env->sc_ibufs[PROC_PARENT], IMSG_PARENT_AUTHENTICATE,
+ 0, 0, -1, &req, sizeof(req));
+
+ return 1;
+}
+
+int
+session_rfc1652_mail_handler(struct session *s, char *args)
+{
+ char *body;
+
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ body = strrchr(args, ' ');
+ if (body != NULL) {
+ *body++ = '\0';
+
+ if (strcasecmp("body=7bit", body) == 0) {
+ s->s_flags &= ~F_8BITMIME;
+ }
+
+ else if (strcasecmp("body=8bitmime", body) != 0) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Invalid BODY\r\n");
+ return 1;
+ }
+
+ return session_rfc5321_mail_handler(s, args);
+ }
+
+ return 0;
+}
+
+int
+session_rfc5321_helo_handler(struct session *s, char *args)
+{
+ void *p;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ if (args == NULL) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 HELO requires domain address.\r\n");
+ return 1;
+ }
+
+ if (strlcpy(s->s_msg.session_helo, args, sizeof(s->s_msg.session_helo))
+ >= sizeof(s->s_msg.session_helo)) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Invalid domain name\r\n");
+ return 1;
+ }
+
+ s->s_state = S_HELO;
+
+ if (s->s_ss.ss_family == PF_INET) {
+ struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss;
+ p = &ssin->sin_addr.s_addr;
+ }
+ if (s->s_ss.ss_family == PF_INET6) {
+ struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss;
+ p = &ssin6->sin6_addr.s6_addr;
+ }
+
+ bzero(addrbuf, sizeof (addrbuf));
+ inet_ntop(s->s_ss.ss_family, p, addrbuf, sizeof (addrbuf));
+
+ evbuffer_add_printf(s->s_bev->output,
+ "250 %s Hello %s [%s%s], pleased to meet you\r\n",
+ s->s_env->sc_hostname, args,
+ s->s_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf);
+
+ return 1;
+}
+
+int
+session_rfc5321_ehlo_handler(struct session *s, char *args)
+{
+ void *p;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ if (args == NULL) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 HELO requires domain address.\r\n");
+ return 1;
+ }
+
+ if (strlcpy(s->s_msg.session_helo, args, sizeof(s->s_msg.session_helo))
+ >= sizeof(s->s_msg.session_helo)) {
+ evbuffer_add_printf(s->s_bev->output,
+ "501 Invalid domain name\r\n");
+ return 1;
+ }
+
+ s->s_state = S_HELO;
+ s->s_flags |= F_8BITMIME;
+
+ if (s->s_ss.ss_family == PF_INET) {
+ struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss;
+ p = &ssin->sin_addr.s_addr;
+ }
+ if (s->s_ss.ss_family == PF_INET6) {
+ struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss;
+ p = &ssin6->sin6_addr.s6_addr;
+ }
+
+ bzero(addrbuf, sizeof (addrbuf));
+ inet_ntop(s->s_ss.ss_family, p, addrbuf, sizeof (addrbuf));
+ evbuffer_add_printf(s->s_bev->output,
+ "250-%s Hello %s [%s%s], pleased to meet you\r\n",
+ s->s_env->sc_hostname, args,
+ s->s_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf);
+
+ evbuffer_add_printf(s->s_bev->output, "250-8BITMIME\r\n");
+
+ /* only advertise starttls if listener can support it */
+ if (s->s_l->flags & F_STARTTLS)
+ evbuffer_add_printf(s->s_bev->output, "250-STARTTLS\r\n");
+
+ /* only advertise auth if session is secure */
+ /*
+ if (s->s_flags & F_SECURE)
+ evbuffer_add_printf(s->s_bev->output, "250-AUTH %s\r\n", "PLAIN");
+ */
+ evbuffer_add_printf(s->s_bev->output, "250 HELP\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_rset_handler(struct session *s, char *args)
+{
+ struct path *pathp;
+
+ while ((pathp = TAILQ_FIRST(&s->s_msg.recipients)) != NULL) {
+ TAILQ_REMOVE(&s->s_msg.recipients, pathp, entry);
+ free(pathp);
+ }
+
+ s->s_msg.rcptcount = 0;
+ s->s_state = S_HELO;
+ evbuffer_add_printf(s->s_bev->output, "250 Reset state.\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_noop_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output, "250 OK.\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_mail_handler(struct session *s, char *args)
+{
+ struct path *pathp;
+ char buffer[MAX_PATH_SIZE];
+
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ if (strlcpy(buffer, args, sizeof(buffer)) >= sizeof(buffer)) {
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 553, "Syntax error for sender address");
+ return 1;
+ }
+
+ if (! session_set_path(&s->s_msg.sender, buffer)) {
+ /* No need to even transmit to MFA, path is invalid */
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 553, "Syntax error for sender address");
+ return 1;
+ }
+
+ while ((pathp = TAILQ_FIRST(&s->s_msg.recipients)) != NULL) {
+ TAILQ_REMOVE(&s->s_msg.recipients, pathp, entry);
+ free(pathp);
+ }
+ s->s_msg.rcptcount = 0;
+
+ s->s_state = S_MAIL;
+ s->s_flags |= F_IMSG_SENT;
+ s->s_msg.id = s->s_id;
+
+ log_debug("session_mail_handler: sending notification to mfa");
+
+ imsg_compose(s->s_env->sc_ibufs[PROC_MFA], IMSG_MFA_RPATH_SUBMIT,
+ 0, 0, -1, &s->s_msg, sizeof(s->s_msg));
+
+ return 1;
+}
+
+int
+session_rfc5321_rcpt_handler(struct session *s, char *args)
+{
+ char buffer[MAX_PATH_SIZE];
+ struct message_recipient mr;
+
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ if (s->s_state == S_HELO) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Need MAIL before RCPT\r\n");
+ return 1;
+ }
+
+ bzero(&mr, sizeof(mr));
+
+ if (strlcpy(buffer, args, sizeof(buffer)) >= sizeof(buffer)) {
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 553, "Syntax error for recipient address");
+ return 1;
+ }
+
+ if (! session_set_path(&mr.path, buffer)) {
+ /* No need to even transmit to MFA, path is invalid */
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 553, "Syntax error for recipient address");
+ return 1;
+ }
+
+ mr.id = s->s_msg.id;
+
+ s->s_state = S_RCPT;
+ s->s_flags |= F_IMSG_SENT;
+
+ mr.ss = s->s_ss;
+
+ imsg_compose(s->s_env->sc_ibufs[PROC_MFA], IMSG_MFA_RCPT_SUBMIT,
+ 0, 0, -1, &mr, sizeof(mr));
+
+ return 1;
+}
+
+int
+session_rfc5321_quit_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output, "221 %s Closing connection.\r\n",
+ s->s_env->sc_hostname);
+
+ s->s_flags |= F_QUIT;
+
+ return 1;
+}
+
+int
+session_rfc5321_data_handler(struct session *s, char *args)
+{
+ if (s->s_state == S_GREETED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Polite people say HELO first\r\n");
+ return 1;
+ }
+
+ if (s->s_state == S_HELO) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Need MAIL before DATA\r\n");
+ return 1;
+ }
+
+ if (s->s_state == S_MAIL) {
+ evbuffer_add_printf(s->s_bev->output,
+ "503 Need RCPT before DATA\r\n");
+ return 1;
+ }
+
+ s->s_state = S_DATA;
+ s->s_flags |= F_IMSG_SENT;
+ s->s_msg.session_id = s->s_id;
+ s->s_msg.session_ss = s->s_ss;
+
+ imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
+ IMSG_QUEUE_CREATE_MESSAGE_FILE, 0, 0, -1, &s->s_msg,
+ sizeof(s->s_msg));
+
+ return 1;
+}
+
+int
+session_rfc5321_vrfy_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output,
+ "252 Cannot VRFY user; try RCPT to attempt delivery.\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_expn_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output,
+ "502 Sorry, we do not allow this operation.\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_turn_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output,
+ "502 Sorry, we do not allow this operation.\r\n");
+
+ return 1;
+}
+
+int
+session_rfc5321_help_handler(struct session *s, char *args)
+{
+ evbuffer_add_printf(s->s_bev->output,
+ "214- This is OpenSMTPD\r\n"
+ "214- To report bugs in the implementation, please contact\r\n"
+ "214- bugs@poolp.org\r\n");
+ evbuffer_add_printf(s->s_bev->output,
+ "214 End of HELP info\r\n");
+
+ return 1;
+}
+
+void
+session_command(struct session *s, char *cmd, char *args)
+{
+ int i;
+
+ /* RFC 1652 - 8BITMIME */
+ for (i = 0; i < (int)(sizeof(rfc1652_cmdtab) / sizeof(struct session_cmd)); ++i)
+ if (strcasecmp(rfc1652_cmdtab[i].name, cmd) == 0)
+ break;
+ if (i < (int)(sizeof(rfc1652_cmdtab) / sizeof(struct session_cmd))) {
+ if (rfc1652_cmdtab[i].func(s, args))
+ return;
+ }
+
+ /* RFC 3207 - STARTTLS */
+ for (i = 0; i < (int)(sizeof(rfc3207_cmdtab) / sizeof(struct session_cmd)); ++i)
+ if (strcasecmp(rfc3207_cmdtab[i].name, cmd) == 0)
+ break;
+ if (i < (int)(sizeof(rfc3207_cmdtab) / sizeof(struct session_cmd))) {
+ if (rfc3207_cmdtab[i].func(s, args))
+ return;
+ }
+
+ /* RFC 4954 - AUTH */
+ /*
+ for (i = 0; i < (int)(sizeof(rfc4954_cmdtab) / sizeof(struct session_cmd)); ++i)
+ if (strcasecmp(rfc4954_cmdtab[i].name, cmd) == 0)
+ break;
+ if (i < (int)(sizeof(rfc4954_cmdtab) / sizeof(struct session_cmd))) {
+ if (rfc4954_cmdtab[i].func(s, args))
+ return;
+ }
+ */
+
+ /* RFC 5321 - SMTP */
+ for (i = 0; i < (int)(sizeof(rfc5321_cmdtab) / sizeof(struct session_cmd)); ++i)
+ if (strcasecmp(rfc5321_cmdtab[i].name, cmd) == 0)
+ break;
+ if (i < (int)(sizeof(rfc5321_cmdtab) / sizeof(struct session_cmd))) {
+ if (rfc5321_cmdtab[i].func(s, args))
+ return;
+ }
+
+ evbuffer_add_printf(s->s_bev->output,
+ "500 Command unrecognized.\r\n");
+}
+
+void
+session_pickup(struct session *s, struct submit_status *ss)
+{
+ struct path *path;
+
+ if (s == NULL)
+ fatal("session_pickup: desynchronized");
+
+ bufferevent_disable(s->s_bev, EV_READ);
+ bufferevent_enable(s->s_bev, EV_WRITE);
+
+ switch (s->s_state) {
+ case S_INIT:
+ s->s_state = S_GREETED;
+ log_debug("session_pickup: greeting client");
+ evbuffer_add_printf(s->s_bev->output,
+ SMTPD_BANNER, s->s_env->sc_hostname);
+ break;
+
+ case S_GREETED:
+ case S_HELO:
+ break;
+
+ case S_TLS:
+ s->s_state = S_GREETED;
+ ssl_session_init(s);
+ bufferevent_disable(s->s_bev, EV_READ|EV_WRITE);
+ break;
+
+ case S_AUTH:
+ if (s->s_flags & F_AUTHENTICATED) {
+ evbuffer_add_printf(s->s_bev->output,
+ "235 Authentication Succeeded\r\n");
+ }
+ else {
+ evbuffer_add_printf(s->s_bev->output,
+ "535 Authentication Credentials Invalid\r\n");
+ }
+ break;
+
+ case S_MAIL:
+ /* sender was not accepted, downgrade state */
+ if (ss->code != 250) {
+ s->s_state = S_HELO;
+ evbuffer_add_printf(s->s_bev->output,
+ "%d Sender rejected\r\n", ss->code);
+ return;
+ }
+
+ s->s_msg.sender = ss->u.path;
+ evbuffer_add_printf(s->s_bev->output,
+ "%d Sender ok\r\n", ss->code);
+ break;
+
+ case S_RCPT:
+ /* recipient was not accepted */
+ if (ss->code != 250) {
+ /* We do not have a valid recipient, downgrade state */
+ if (s->s_msg.rcptcount == 0)
+ s->s_state = S_MAIL;
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", ss->code, "Recipient rejected");
+ return;
+ }
+
+ path = calloc(1, sizeof(struct path));
+ if (path == NULL)
+ err(1, "calloc");
+ *path = ss->u.path;
+ TAILQ_INSERT_TAIL(&s->s_msg.recipients, path, entry);
+ s->s_msg.rcptcount++;
+ evbuffer_add_printf(s->s_bev->output,
+ "%d Recipient ok\r\n", ss->code);
+ break;
+
+ case S_DATA:
+ if (s->s_msg.datafp == NULL) {
+ /* Remove message file */
+ imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_DELETE_MESSAGE_FILE,
+ 0, 0, -1, &s->s_msg, sizeof(s->s_msg));
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 421,
+ "Service temporarily unavailable");
+ return;
+ }
+ s->s_state = S_DATACONTENT;
+ evbuffer_add_printf(s->s_bev->output,
+ "%d %s\r\n", 354,
+ "Enter mail, end with \".\" on a line by itself");
+ break;
+
+ case S_DATACONTENT:
+ break;
+
+ case S_DONE:
+ s->s_msg.rcptcount--;
+ if (s->s_msg.rcptcount)
+ return;
+
+ s->s_state = S_HELO;
+ s->s_msg.datafp = NULL;
+ evbuffer_add_printf(s->s_bev->output,
+ "250 %s Message accepted for delivery\r\n",
+ s->s_msg.message_id);
+
+ break;
+
+ default:
+ log_debug("session_pickup: state value: %d", s->s_state);
+ fatal("session_pickup: unknown state");
+ break;
+ }
+}
+
+void
+session_init(struct listener *l, struct session *s)
+{
+ s->s_state = S_INIT;
+ s->s_env = l->env;
+ s->s_l = l;
+ s->s_id = queue_generate_id();
+
+ strlcpy(s->s_hostname, "<unknown>", MAXHOSTNAMELEN);
+
+ TAILQ_INIT(&s->s_msg.recipients);
+
+ SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s);
+
+ imsg_compose(s->s_env->sc_ibufs[PROC_LKA], IMSG_LKA_HOSTNAME_LOOKUP,
+ 0, 0, -1, s, sizeof(struct session));
+
+ if ((s->s_bev = bufferevent_new(s->s_fd, session_read, session_write,
+ session_error, s)) == NULL)
+ fatal(NULL);
+
+ if (l->flags & F_SSMTP) {
+ log_debug("session_init: initializing ssl");
+ ssl_session_init(s);
+ return;
+ }
+
+ session_pickup(s, NULL);
+}
+
+void
+session_read(struct bufferevent *bev, void *p)
+{
+ struct session *s = p;
+ char *line;
+ char *ep;
+ char *args;
+
+read:
+ s->s_tm = time(NULL);
+ s->s_flags &= ~F_IMSG_SENT;
+ line = evbuffer_readline(bev->input);
+ if (line == NULL) {
+ bufferevent_disable(s->s_bev, EV_READ);
+ bufferevent_enable(s->s_bev, EV_WRITE);
+ return;
+ }
+
+ if (s->s_state == S_DATACONTENT) {
+ line[strcspn(line, "\r")] = '\0';
+ if (strcmp(line, ".") == 0) {
+ s->s_state = S_DONE;
+ fclose(s->s_msg.datafp);
+
+ if (s->s_msg.status & S_MESSAGE_PERMFAILURE) {
+ evbuffer_add_printf(s->s_bev->output,
+ "554 Transaction failed\r\n");
+
+ s->s_msg.datafp = NULL;
+ /* Remove message file */
+ imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_DELETE_MESSAGE_FILE,
+ 0, 0, -1, &s->s_msg, sizeof(s->s_msg));
+ bufferevent_enable(s->s_bev, EV_WRITE);
+ free(line);
+ return;
+ }
+ bufferevent_disable(s->s_bev, EV_READ);
+ session_msg_submit(s);
+ free(line);
+ return;
+ } else {
+ size_t i;
+ size_t len;
+
+ len = strlen(line);
+ fwrite(line, len, 1, s->s_msg.datafp);
+ fwrite("\n", 1, 1, s->s_msg.datafp);
+ fflush(s->s_msg.datafp);
+
+ if (! (s->s_flags & F_8BITMIME)) {
+ for (i = 0; i < len; ++i) {
+ if (line[i] & 0x80) {
+ s->s_msg.status |= S_MESSAGE_PERMFAILURE;
+ strlcpy(s->s_msg.session_errorline, "8BIT data transfered over 7BIT limited channel",
+ sizeof s->s_msg.session_errorline);
+ }
+ }
+ }
+ }
+ goto read;
+ }
+ bufferevent_disable(s->s_bev, EV_READ);
+ bufferevent_enable(s->s_bev, EV_WRITE);
+
+ line[strcspn(line, "\r")] = '\0';
+ if ((ep = strchr(line, ':')) == NULL)
+ ep = strchr(line, ' ');
+ if (ep != NULL) {
+ *ep = '\0';
+ args = ++ep;
+ while (isspace((int)*args))
+ args++;
+ } else
+ args = NULL;
+ log_debug("command: %s\targs: %s", line, args);
+ session_command(s, line, args);
+ free(line);
+ return;
+}
+
+void
+session_write(struct bufferevent *bev, void *p)
+{
+ struct session *s = p;
+
+ if (!(s->s_flags & F_QUIT)) {
+ if (! EVBUFFER_LENGTH(EVBUFFER_OUTPUT(bev))) {
+ bufferevent_disable(s->s_bev, EV_WRITE);
+ bufferevent_enable(s->s_bev, EV_READ);
+ }
+
+ if (s->s_state == S_TLS)
+ session_pickup(s, NULL);
+
+ return;
+ }
+
+ if (! EVBUFFER_LENGTH(EVBUFFER_OUTPUT(bev))) {
+ session_destroy(s);
+ }
+}
+
+void
+session_destroy(struct session *s)
+{
+ struct path *pathp;
+
+ /*
+ * cleanup
+ */
+ log_debug("session_destroy: killing client");
+ close(s->s_fd);
+
+ if (s->s_msg.datafp != NULL) {
+ fclose(s->s_msg.datafp);
+ s->s_msg.datafp = NULL;
+ /* Remove message file */
+ imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_DELETE_MESSAGE_FILE,
+ 0, 0, -1, &s->s_msg, sizeof(s->s_msg));
+ }
+
+ if (s->s_bev != NULL) {
+ bufferevent_disable(s->s_bev, EV_READ|EV_WRITE);
+ bufferevent_free(s->s_bev);
+ }
+ ssl_session_destroy(s);
+
+ while ((pathp = TAILQ_FIRST(&s->s_msg.recipients)) != NULL) {
+ TAILQ_REMOVE(&s->s_msg.recipients, pathp, entry);
+ free(pathp);
+ }
+
+ SPLAY_REMOVE(sessiontree, &s->s_env->sc_sessions, s);
+ bzero(s, sizeof(*s));
+ free(s);
+}
+
+void
+session_error(struct bufferevent *bev, short event, void *p)
+{
+ struct session *s = p;
+
+ session_destroy(s);
+}
+
+void
+session_msg_submit(struct session *s)
+{
+ struct path *rpath;
+
+ strlcpy(s->s_msg.session_hostname, s->s_hostname, MAXHOSTNAMELEN);
+ TAILQ_FOREACH(rpath, &s->s_msg.recipients, entry) {
+ s->s_msg.recipient = *rpath;
+ imsg_compose(s->s_env->sc_ibufs[PROC_QUEUE],
+ IMSG_QUEUE_MESSAGE_SUBMIT, 0, 0, -1, &s->s_msg,
+ sizeof(s->s_msg));
+ }
+}
+
+int
+session_cmp(struct session *s1, struct session *s2)
+{
+ /*
+ * do not return u_int64_t's
+ */
+ if (s1->s_id < s2->s_id)
+ return (-1);
+
+ if (s1->s_id > s2->s_id)
+ return (1);
+
+ return (0);
+}
+
+int
+session_set_path(struct path *path, char *line)
+{
+ size_t len;
+ char *username;
+ char *hostname;
+
+ len = strlen(line);
+ if (*line != '<' || line[len - 1] != '>')
+ return 0;
+ line[len - 1] = '\0';
+
+ username = line + 1;
+ hostname = strchr(username, '@');
+
+ if (username[0] == '\0') {
+ *path->user = '\0';
+ *path->domain = '\0';
+ return 1;
+ }
+
+ if (hostname == NULL) {
+ if (strcasecmp(username, "postmaster") != 0)
+ return 0;
+ hostname = "localhost";
+ } else {
+ *hostname++ = '\0';
+ }
+
+ if (strlcpy(path->user, username, sizeof(path->user))
+ >= MAX_LOCALPART_SIZE)
+ return 0;
+
+ if (strlcpy(path->domain, hostname, sizeof(path->domain))
+ >= MAX_DOMAINPART_SIZE)
+ return 0;
+
+ return 1;
+}
+
+void
+session_timeout(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct session *sessionp;
+ struct session *rmsession;
+ struct timeval tv;
+ time_t tm;
+ u_int8_t i;
+
+ tm = time(NULL);
+ rmsession = NULL;
+ SPLAY_FOREACH(sessionp, sessiontree, &env->sc_sessions) {
+
+ if (rmsession != NULL)
+ session_destroy(rmsession);
+
+ for (i = 0; i < sizeof (rfc5321_timeouttab) /
+ sizeof(struct session_timeout); ++i)
+ if (rfc5321_timeouttab[i].state == sessionp->s_state)
+ break;
+
+ if (i == sizeof (rfc5321_timeouttab) / sizeof (struct session_timeout)) {
+ if (tm - SMTPD_SESSION_TIMEOUT < sessionp->s_tm)
+ continue;
+ }
+ else if (tm - rfc5321_timeouttab[i].timeout < sessionp->s_tm) {
+ continue;
+ }
+
+ rmsession = sessionp;
+ }
+
+ if (rmsession != NULL)
+ session_destroy(rmsession);
+
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ evtimer_add(&env->sc_ev, &tv);
+}
+
+
+SPLAY_GENERATE(sessiontree, session, s_nodes, session_cmp);
diff --git a/usr.sbin/smtpd/smtpd.8 b/usr.sbin/smtpd/smtpd.8
new file mode 100644
index 00000000000..e0632198d15
--- /dev/null
+++ b/usr.sbin/smtpd/smtpd.8
@@ -0,0 +1,99 @@
+.\" $OpenBSD: smtpd.8,v 1.1 2008/11/01 21:35:28 gilles Exp $
+.\"
+.\" Copyright (c) 2008, Gilles Chehade <gilles@openbsd.org>
+.\" Copyright (c) 2008, 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.
+.\"
+.Dd $Mdocdate: November 1 2008 $
+.Dt SMTPD 8
+.Os
+.Sh NAME
+.Nm smtpd
+.Nd "Simple Mail Transfer Protocol daemon"
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Oo Fl D Ar macro Ns =
+.Ar value Oc
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a Simple Mail Transfer Protocol
+.Pq SMTP
+daemon which can be used as a machine's primary mail system.
+.Nm
+can listen on a network interface and handle
+.Pq SMTP
+transactions, it can also be fed messages through the standard
+.Xr sendmail 8
+interface.
+.Nm
+can relay messages through remote mail transfer agents or store them
+locally using either the mbox or maildir format.
+This implementation supports SMTP as defined by RFC5321 as well as several
+extensions.
+A running
+.Nm
+can be controlled through
+.Xr smtpctl 8
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl D Ar macro Ns = Ns Ar value
+Define
+.Ar macro
+to be set to
+.Ar value
+on the command line.
+Overrides the definition of
+.Ar macro
+in the configuration file.
+.It Fl d
+Do not daemonize.
+If this option is specified,
+.Nm
+will run in the foreground and log to
+.Em stderr .
+.It Fl f Ar file
+Specify an altenative configuration file.
+.It Fl n
+Configtest mode.
+Only check the configuration file for validity.
+.It Fl v
+Produce more verbose output.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/smtpd.sockXX" -compact
+.It /etc/mail/smtpd.conf
+Default
+.Nm
+configuration file.
+.It /var/run/smtpd.sock
+Unix-domain socket used for communication with
+.Xr smtpctl 8 .
+.El
+.Sh SEE ALSO
+.Xr smtpd.conf 5 ,
+.Xr smtpctl 8 ,
+.Xr smtpdb 8
+.Rs
+.%R RFC 5321
+.%T "SMTP"
+.%D April 2001
+.Re
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 4.4 .
diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c
new file mode 100644
index 00000000000..05d2f6f550b
--- /dev/null
+++ b/usr.sbin/smtpd/smtpd.c
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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/queue.h>
+#include <sys/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+__dead void usage(void);
+void parent_shutdown(void);
+void parent_send_config(int, short, void *);
+void parent_dispatch_lka(int, short, void *);
+void parent_dispatch_mda(int, short, void *);
+void parent_dispatch_mfa(int, short, void *);
+void parent_dispatch_smtp(int, short, void *);
+void parent_sig_handler(int, short, void *);
+int parent_open_message_file(struct batch *);
+int parent_open_mailbox(struct batch *, struct path *);
+int parent_open_filename(struct batch *, struct path *);
+int parent_rename_mailfile(struct batch *);
+int parent_open_maildir(struct batch *, struct path *);
+int parent_maildir_init(struct passwd *, char *);
+int parent_external_mda(struct batch *, struct path *);
+int check_child(pid_t, const char *);
+int setup_spool(uid_t, gid_t);
+
+pid_t lka_pid = 0;
+pid_t mfa_pid = 0;
+pid_t queue_pid = 0;
+pid_t mda_pid = 0;
+pid_t mta_pid = 0;
+pid_t control_pid = 0;
+pid_t smtp_pid = 0;
+
+
+int __b64_pton(char const *, unsigned char *, size_t);
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-n] [-f config]\n", __progname);
+ exit(1);
+}
+
+void
+parent_shutdown(void)
+{
+ u_int i;
+ pid_t pid;
+ pid_t pids[] = {
+ lka_pid,
+ mfa_pid,
+ queue_pid,
+ mda_pid,
+ mta_pid,
+ control_pid,
+ smtp_pid
+ };
+
+ for (i = 0; i < sizeof(pids) / sizeof(pid); i++)
+ if (pids[i])
+ kill(pids[i], SIGTERM);
+
+ do {
+ if ((pid = wait(NULL)) == -1 &&
+ errno != EINTR && errno != ECHILD)
+ fatal("wait");
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ log_info("terminating");
+ exit(0);
+}
+
+void
+parent_send_config(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct buf *b;
+ struct listener *l;
+ struct ssl *s;
+
+ log_debug("parent_send_config: configuring smtp");
+ imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_CONF_START,
+ 0, 0, -1, NULL, 0);
+
+ SPLAY_FOREACH(s, ssltree, &env->sc_ssl) {
+ b = imsg_create(env->sc_ibufs[PROC_SMTP], IMSG_CONF_SSL, 0, 0,
+ sizeof(*s) + s->ssl_cert_len + s->ssl_key_len);
+ if (b == NULL)
+ fatal("imsg_create");
+ if (imsg_add(b, s, sizeof(*s)) == -1)
+ fatal("imsg_add: ssl");
+ if (imsg_add(b, s->ssl_cert, s->ssl_cert_len) == -1)
+ fatal("imsg_add: ssl_cert");
+ if (imsg_add(b, s->ssl_key, s->ssl_key_len) == -1)
+ fatal("imsg_add: ssl_key");
+ b->fd = -1;
+ if (imsg_close(env->sc_ibufs[PROC_SMTP], b) == -1)
+ fatal("imsg_close");
+ }
+
+ TAILQ_FOREACH(l, &env->sc_listeners, entry) {
+ smtp_listener_setup(env, l);
+ imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_CONF_LISTENER,
+ 0, 0, l->fd, l, sizeof(*l));
+ }
+ imsg_compose(env->sc_ibufs[PROC_SMTP], IMSG_CONF_END,
+ 0, 0, -1, NULL, 0);
+}
+
+void
+parent_dispatch_lka(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_LKA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("parent_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+parent_dispatch_mfa(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MFA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_lka: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ default:
+ log_debug("parent_dispatch_lka: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+parent_dispatch_mda(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_MDA];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_mda: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ case IMSG_PARENT_MAILBOX_OPEN: {
+ struct batch *batchp;
+ struct path *path;
+ u_int8_t i;
+ int desc;
+ struct action_handler {
+ enum action_type action;
+ int (*handler)(struct batch *, struct path *);
+ } action_hdl_table[] = {
+ { A_MBOX, parent_open_mailbox },
+ { A_MAILDIR, parent_open_maildir },
+ { A_EXT, parent_external_mda },
+ { A_FILENAME, parent_open_filename }
+ };
+
+ batchp = imsg.data;
+ path = &batchp->message.recipient;
+ if (batchp->type & T_DAEMON_BATCH) {
+ path = &batchp->message.sender;
+ }
+
+ for (i = 0; i < sizeof(action_hdl_table) / sizeof(struct action_handler); ++i) {
+ if (action_hdl_table[i].action == path->rule.r_action) {
+ desc = action_hdl_table[i].handler(batchp, path);
+ imsg_compose(ibuf, IMSG_MDA_MAILBOX_FILE, 0, 0,
+ desc, batchp, sizeof(struct batch));
+ break;
+ }
+ }
+ if (i == sizeof(action_hdl_table) / sizeof(struct action_handler))
+ errx(1, "%s: unknown action.", __func__);
+
+ break;
+ }
+ case IMSG_PARENT_MESSAGE_OPEN: {
+ struct batch *batchp;
+ int desc;
+
+ batchp = imsg.data;
+ desc = parent_open_message_file(batchp);
+ imsg_compose(ibuf, IMSG_MDA_MESSAGE_FILE, 0, 0,
+ desc, batchp, sizeof(struct batch));
+
+ break;
+ }
+ case IMSG_PARENT_MAILBOX_RENAME: {
+ struct batch *batchp;
+
+ batchp = imsg.data;
+ parent_rename_mailfile(batchp);
+
+ break;
+ }
+ default:
+ log_debug("parent_dispatch_mda: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+parent_dispatch_smtp(int fd, short event, void *p)
+{
+ struct smtpd *env = p;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+
+ ibuf = env->sc_ibufs[PROC_SMTP];
+ switch (event) {
+ case EV_READ:
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal("imsg_read_error");
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&ibuf->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ break;
+ case EV_WRITE:
+ if (msgbuf_write(&ibuf->w) == -1)
+ fatal("msgbuf_write");
+ imsg_event_add(ibuf);
+ return;
+ default:
+ fatalx("unknown event");
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("parent_dispatch_smtp: imsg_read error");
+ if (n == 0)
+ break;
+
+ switch (imsg.hdr.type) {
+ /* XXX - NOT ADVERTISED YET */
+ case IMSG_PARENT_AUTHENTICATE: {
+ struct session_auth_req *req;
+ struct session_auth_reply reply;
+ u_int8_t buffer[1024];
+ char *pw_name;
+ char *pw_passwd;
+ struct passwd *pw;
+
+ req = (struct session_auth_req *)imsg.data;
+
+ reply.session_id = req->session_id;
+ reply.value = 0;
+
+ if (__b64_pton(req->buffer, buffer, 1024) >= 0) {
+ pw_name = buffer+1;
+ pw_passwd = pw_name+strlen(pw_name)+1;
+ pw = getpwnam(pw_name);
+ if (pw != NULL)
+ if (strcmp(pw->pw_passwd, crypt(pw_passwd,
+ pw->pw_passwd)) == 0)
+ reply.value = 1;
+ }
+ imsg_compose(ibuf, IMSG_PARENT_AUTHENTICATE, 0, 0,
+ -1, &reply, sizeof(reply));
+
+ break;
+ }
+ default:
+ log_debug("parent_dispatch_smtp: unexpected imsg %d",
+ imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(ibuf);
+}
+
+void
+parent_sig_handler(int sig, short event, void *p)
+{
+ int i;
+ int die = 0;
+ pid_t pid;
+ struct mdaproc *mdaproc;
+ struct mdaproc lookup;
+ struct smtpd *env = p;
+ struct { pid_t p; const char *s; } procs[] = {
+ { lka_pid, "lookup agent" },
+ { mfa_pid, "mail filter agent" },
+ { queue_pid, "mail queue" },
+ { mda_pid, "mail delivery agent" },
+ { mta_pid, "mail transfer agent" },
+ { control_pid, "control process" },
+ { smtp_pid, "smtp server" },
+ { 0, NULL },
+ };
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ /* FALLTHROUGH */
+ case SIGCHLD:
+ for (i = 0; procs[i].s != NULL; i++)
+ if (check_child(procs[i].p, procs[i].s)) {
+ procs[i].p = 0;
+ die = 1;
+ }
+ if (die)
+ parent_shutdown();
+
+ do {
+ int status;
+
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid > 0) {
+ lookup.pid = pid;
+ mdaproc = SPLAY_FIND(mdaproctree, &env->mdaproc_queue, &lookup);
+ if (mdaproc == NULL)
+ errx(1, "received SIGCHLD but no known child for that pid (#%d)", pid);
+
+ if (WIFEXITED(status) && !WIFSIGNALED(status)) {
+ switch (WEXITSTATUS(status)) {
+ case EX_OK:
+ log_debug("DEBUG: external mda reported success");
+ break;
+ case EX_TEMPFAIL:
+ log_debug("DEBUG: external mda reported temporary failure");
+ break;
+ default:
+ log_debug("DEBUG: external mda reported permanent failure");
+ }
+ }
+ else {
+ log_debug("DEBUG: external mda process has terminated in a baaaad way");
+ }
+
+ free(mdaproc);
+ }
+ } while (pid > 0 || (pid == -1 && errno == EINTR));
+
+ /**/
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int debug;
+ int opts;
+ const char *conffile = CONF_FILE;
+ struct smtpd env;
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+ struct timeval tv;
+ struct peer peers[] = {
+ { PROC_LKA, parent_dispatch_lka },
+ { PROC_MDA, parent_dispatch_mda },
+ { PROC_MFA, parent_dispatch_mfa },
+ { PROC_SMTP, parent_dispatch_smtp },
+ };
+
+ opts = 0;
+ debug = 0;
+
+ log_init(1);
+
+ while ((c = getopt(argc, argv, "dD:nf:v")) != -1) {
+ switch (c) {
+ case 'd':
+ debug = 2;
+ break;
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'n':
+ debug = 2;
+ opts |= SMTPD_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ opts |= SMTPD_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (parse_config(&env, conffile, opts))
+ exit(1);
+
+ if (env.sc_opts & SMTPD_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ /* check for root privileges */
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL)
+ errx(1, "unknown user %s", SMTPD_USER);
+ endpwent();
+
+ if (!setup_spool(env.sc_pw->pw_uid, 0))
+ errx(1, "invalid directory permissions");
+
+ log_init(debug);
+
+ if (!debug) {
+ if (daemon(1, 0) == -1)
+ err(1, "failed to daemonize");
+ }
+
+ log_info("startup%s", (debug > 1)?" [debug mode]":"");
+
+ init_peers(&env);
+
+ /* start subprocesses */
+ lka_pid = lka(&env);
+ mfa_pid = mfa(&env);
+ queue_pid = queue(&env);
+ mda_pid = mda(&env);
+ mta_pid = mta(&env);
+ smtp_pid = smtp(&env);
+ control_pid = control(&env);
+
+ setproctitle("parent");
+ SPLAY_INIT(&env.mdaproc_queue);
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, parent_sig_handler, &env);
+ signal_set(&ev_sigterm, SIGTERM, parent_sig_handler, &env);
+ signal_set(&ev_sigchld, SIGCHLD, parent_sig_handler, &env);
+ signal_set(&ev_sighup, SIGHUP, parent_sig_handler, &env);
+ signal_add(&ev_sigint, NULL);
+ signal_add(&ev_sigterm, NULL);
+ signal_add(&ev_sigchld, NULL);
+ signal_add(&ev_sighup, NULL);
+ signal(SIGPIPE, SIG_IGN);
+
+ config_peers(&env, peers, 4);
+
+ evtimer_set(&env.sc_ev, parent_send_config, &env);
+ bzero(&tv, sizeof(tv));
+ evtimer_add(&env.sc_ev, &tv);
+
+ event_dispatch();
+
+ return (0);
+}
+
+
+int
+check_child(pid_t pid, const char *pname)
+{
+ int status;
+
+ if (waitpid(pid, &status, WNOHANG) > 0) {
+ if (WIFEXITED(status)) {
+ log_warnx("check_child: lost child: %s exited", pname);
+ return (1);
+ }
+ if (WIFSIGNALED(status)) {
+ log_warnx("check_child: lost child: %s terminated; "
+ "signal %d", pname, WTERMSIG(status));
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+int
+setup_spool(uid_t uid, gid_t gid)
+{
+ unsigned int n;
+ char *paths[] = { PATH_MESSAGES, PATH_LOCAL, PATH_RELAY,
+ PATH_DAEMON, PATH_ENVELOPES };
+ char pathname[MAXPATHLEN];
+ struct stat sb;
+ int ret;
+
+ if (snprintf(pathname, MAXPATHLEN, "%s", PATH_SPOOL) >= MAXPATHLEN)
+ fatal("snprintf");
+
+ if (stat(pathname, &sb) == -1) {
+ if (errno != ENOENT) {
+ warn("stat: %s", pathname);
+ return 0;
+ }
+
+ if (mkdir(pathname, 0711) == -1) {
+ warn("mkdir: %s", pathname);
+ return 0;
+ }
+
+ if (chown(pathname, 0, 0) == -1) {
+ warn("chown: %s", pathname);
+ return 0;
+ }
+
+ if (stat(pathname, &sb) == -1)
+ err(1, "stat: %s", pathname);
+ }
+
+ /* check if it's a directory */
+ if (!S_ISDIR(sb.st_mode)) {
+ warnx("%s is not a directory", pathname);
+ return 0;
+ }
+
+ /* check that it is owned by uid/gid */
+ if (sb.st_uid != 0 || sb.st_gid != 0) {
+ warnx("%s must be owned by root:wheel", pathname);
+ return 0;
+ }
+
+ /* check permission */
+ if ((sb.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR)) != (S_IRUSR|S_IWUSR|S_IXUSR) ||
+ (sb.st_mode & (S_IRGRP|S_IWGRP|S_IXGRP)) != S_IXGRP ||
+ (sb.st_mode & (S_IROTH|S_IWOTH|S_IXOTH)) != S_IXOTH) {
+ warnx("%s must be rwx--x--x (0711)", pathname);
+ return 0;
+ }
+
+ ret = 1;
+ for (n = 0; n < sizeof(paths)/sizeof(paths[0]); n++) {
+ if (snprintf(pathname, MAXPATHLEN, "%s%s", PATH_SPOOL,
+ paths[n]) >= MAXPATHLEN)
+ fatal("snprintf");
+
+ if (stat(pathname, &sb) == -1) {
+ if (errno != ENOENT) {
+ warn("stat: %s", pathname);
+ ret = 0;
+ continue;
+ }
+
+ if (mkdir(pathname, 0700) == -1) {
+ ret = 0;
+ warn("mkdir: %s", pathname);
+ }
+
+ if (chown(pathname, uid, gid) == -1) {
+ ret = 0;
+ warn("chown: %s", pathname);
+ }
+
+ if (stat(pathname, &sb) == -1)
+ err(1, "stat: %s", pathname);
+ }
+
+ /* check if it's a directory */
+ if (!S_ISDIR(sb.st_mode)) {
+ ret = 0;
+ warnx("%s is not a directory", pathname);
+ }
+
+ /* check that it is owned by uid/gid */
+ if (sb.st_uid != uid) {
+ ret = 0;
+ warnx("%s is not owned by uid %d", pathname, uid);
+ }
+ if (sb.st_gid != gid) {
+ ret = 0;
+ warnx("%s is not owned by gid %d", pathname, gid);
+ }
+
+ /* check permission */
+ if ((sb.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR)) != (S_IRUSR|S_IWUSR|S_IXUSR) ||
+ (sb.st_mode & (S_IRGRP|S_IWGRP|S_IXGRP)) ||
+ (sb.st_mode & (S_IROTH|S_IWOTH|S_IXOTH))) {
+ ret = 0;
+ warnx("%s must be rwx------ (0700)", pathname);
+ }
+ }
+ return ret;
+}
+
+void
+imsg_event_add(struct imsgbuf *ibuf)
+{
+ if (ibuf->handler == NULL) {
+ imsg_flush(ibuf);
+ return;
+ }
+
+ ibuf->events = EV_READ;
+ if (ibuf->w.queued)
+ ibuf->events |= EV_WRITE;
+
+ event_del(&ibuf->ev);
+ event_set(&ibuf->ev, ibuf->fd, ibuf->events, ibuf->handler, ibuf->data);
+ event_add(&ibuf->ev, NULL);
+}
+
+int
+parent_open_message_file(struct batch *batchp)
+{
+ int fd;
+ char pathname[MAXPATHLEN];
+
+ if (snprintf(pathname, MAXPATHLEN, "%s%s/%s",
+ PATH_SPOOL, PATH_MESSAGES, batchp->message_id)
+ >= MAXPATHLEN) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ fd = open(pathname, O_RDONLY);
+ return fd;
+}
+
+int
+parent_open_mailbox(struct batch *batchp, struct path *path)
+{
+ int fd;
+ struct passwd *pw;
+ char pathname[MAXPATHLEN];
+
+ pw = getpwnam(path->pw_name);
+ if (pw == NULL) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ (void)snprintf(pathname, MAXPATHLEN, "%s", path->rule.r_value.path);
+
+ fd = open(pathname, O_CREAT|O_APPEND|O_RDWR|O_EXLOCK|O_SYNC|O_NONBLOCK, 0600);
+ if (fd == -1) {
+ /* XXX - this needs to be discussed ... */
+ switch (errno) {
+ case ENOTDIR:
+ case ENOENT:
+ case EACCES:
+ case ELOOP:
+ case EROFS:
+ case EDQUOT:
+ case EINTR:
+ case EIO:
+ case EMFILE:
+ case ENFILE:
+ case ENOSPC:
+ case EWOULDBLOCK:
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ break;
+ default:
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ }
+ return -1;
+ }
+
+ fchown(fd, pw->pw_uid, 0);
+
+ return fd;
+}
+
+
+int
+parent_open_maildir(struct batch *batchp, struct path *path)
+{
+ int fd;
+ struct passwd *pw;
+ char pathname[MAXPATHLEN];
+
+ pw = getpwnam(path->pw_name);
+ if (pw == NULL) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ snprintf(pathname, MAXPATHLEN, "%s", path->rule.r_value.path);
+ log_debug("PATH: %s", pathname);
+ if (! parent_maildir_init(pw, pathname)) {
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ return -1;
+ }
+
+ if (snprintf(pathname, MAXPATHLEN, "%s/tmp/%s",
+ pathname, batchp->message.message_uid) >= MAXPATHLEN) {
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ return -1;
+ }
+
+ fd = open(pathname, O_CREAT|O_RDWR|O_TRUNC|O_EXLOCK|O_SYNC, 0600);
+ if (fd == -1) {
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ return -1;
+ }
+
+ fchown(fd, pw->pw_uid, pw->pw_gid);
+
+ return fd;
+}
+
+int
+parent_maildir_init(struct passwd *pw, char *root)
+{
+ u_int8_t i;
+ char pathname[MAXPATHLEN];
+ char *subdir[] = { "/", "/tmp", "/cur", "/new" };
+
+ for (i = 0; i < sizeof (subdir) / sizeof (char *); ++i) {
+ if (snprintf(pathname, MAXPATHLEN, "%s%s", root, subdir[i])
+ >= MAXPATHLEN)
+ return 0;
+ if (mkdir(pathname, 0700) == -1)
+ if (errno != EEXIST)
+ return 0;
+ chown(pathname, pw->pw_uid, pw->pw_gid);
+ }
+
+ return 1;
+}
+
+int
+parent_rename_mailfile(struct batch *batchp)
+{
+ struct passwd *pw;
+ char srcpath[MAXPATHLEN];
+ char dstpath[MAXPATHLEN];
+ struct path *path;
+
+ if (batchp->type & T_DAEMON_BATCH) {
+ path = &batchp->message.sender;
+ }
+ else {
+ path = &batchp->message.recipient;
+ }
+
+ pw = getpwnam(path->pw_name);
+ if (pw == NULL) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return 0;
+ }
+
+ (void)snprintf(srcpath, MAXPATHLEN, "%s/tmp/%s",
+ path->rule.r_value.path, batchp->message.message_uid);
+ (void)snprintf(dstpath, MAXPATHLEN, "%s/new/%s",
+ path->rule.r_value.path, batchp->message.message_uid);
+
+ if (rename(srcpath, dstpath) == -1) {
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+parent_external_mda(struct batch *batchp, struct path *path)
+{
+ struct passwd *pw;
+ pid_t pid;
+ int pipefd[2];
+ struct mdaproc *mdaproc;
+ char *pw_name;
+
+ pw_name = path->pw_name;
+ if (pw_name[0] == '\0')
+ pw_name = SMTPD_USER;
+
+ log_debug("executing filter as user: %s", pw_name);
+ pw = getpwnam(pw_name);
+ if (pw == NULL) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd) == -1) {
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ return -1;
+ }
+
+ if (pid == 0) {
+ setproctitle("external MDA");
+
+ 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("mta: cannot drop privileges");
+
+ close(pipefd[0]);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ dup2(pipefd[1], 0);
+
+ execlp(_PATH_BSHELL, "sh", "-c", path->rule.r_value.path, (void *)NULL);
+ exit(1);
+ }
+
+ mdaproc = calloc(1, sizeof (struct mdaproc));
+ if (mdaproc == NULL)
+ err(1, "calloc");
+ mdaproc->pid = pid;
+
+ SPLAY_INSERT(mdaproctree, &batchp->env->mdaproc_queue, mdaproc);
+
+ close(pipefd[1]);
+ return pipefd[0];
+}
+
+int
+parent_open_filename(struct batch *batchp, struct path *path)
+{
+ int fd;
+ char pathname[MAXPATHLEN];
+
+ (void)snprintf(pathname, MAXPATHLEN, "%s", path->u.filename);
+ fd = open(pathname, O_CREAT|O_APPEND|O_RDWR|O_EXLOCK|O_SYNC|O_NONBLOCK, 0600);
+ if (fd == -1) {
+ /* XXX - this needs to be discussed ... */
+ switch (errno) {
+ case ENOTDIR:
+ case ENOENT:
+ case EACCES:
+ case ELOOP:
+ case EROFS:
+ case EDQUOT:
+ case EINTR:
+ case EIO:
+ case EMFILE:
+ case ENFILE:
+ case ENOSPC:
+ case EWOULDBLOCK:
+ batchp->message.status |= S_MESSAGE_TEMPFAILURE;
+ break;
+ default:
+ batchp->message.status |= S_MESSAGE_PERMFAILURE;
+ }
+ return -1;
+ }
+
+ return fd;
+}
+
+int
+mdaproc_cmp(struct mdaproc *s1, struct mdaproc *s2)
+{
+ if (s1->pid < s2->pid)
+ return (-1);
+
+ if (s1->pid > s2->pid)
+ return (1);
+
+ return (0);
+}
+
+SPLAY_GENERATE(mdaproctree, mdaproc, mdaproc_nodes, mdaproc_cmp);
diff --git a/usr.sbin/smtpd/smtpd.conf b/usr.sbin/smtpd/smtpd.conf
new file mode 100644
index 00000000000..0797d5e93c5
--- /dev/null
+++ b/usr.sbin/smtpd/smtpd.conf
@@ -0,0 +1,11 @@
+listen on localhost port 25
+#ssmtp listen on localhost port 465
+hostname localhost
+
+local = "( 127.0.0.1, ::1 )"
+
+#map "aliases" { source db "/etc/mail/aliases.db" }
+#map "virtual" { source db "/etc/mail/virtual.db" }
+
+accept for domain "localhost" deliver to mbox "/var/mail/%u"
+accept from $local for all relay
diff --git a/usr.sbin/smtpd/smtpd.conf.5 b/usr.sbin/smtpd/smtpd.conf.5
new file mode 100644
index 00000000000..287e306c5e7
--- /dev/null
+++ b/usr.sbin/smtpd/smtpd.conf.5
@@ -0,0 +1,101 @@
+.\" $OpenBSD: smtpd.conf.5,v 1.1 2008/11/01 21:35:28 gilles Exp $
+.\"
+.\" Copyright (c) 2008 Janne Johansson <jj@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.
+.\"
+.\"
+.Dd $Mdocdate: November 1 2008 $
+.Dt SMTPD.CONF 5
+.Os
+.Sh NAME
+.Nm smtpd.conf
+.Nd Configuration for smtpd
+.Sh DESCRIPTION
+.Nm
+is used to configure the
+.Xr smtpd 8 ,
+a small SMTP daemon.
+.Pp
+.Sh SMTPD.CONF FILE FORMAT
+Lines beginning with
+.Sq #
+and empty lines are regarded as comments,
+and ignored.
+Lines may be split using the
+.Sq \e
+character.
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/mail/sub.smtpd.conf"
+.Ed
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, and may contain letters, digits
+and underscores.
+Macro names may not be reserved words (for example
+.Ar listen ,
+.Ar accept ,
+.Ar port ) .
+Macros are not expanded inside quotes.
+.Pp
+For example,
+.Bd -literal -offset indent
+smtp_port = 25
+listen on 127.0.0.1 port $smtp_port
+.Ed
+.Sh EXPANSION
+Some configuration directives expect expansion of their parameter at
+runtime. Such directives (for example
+.Ar deliver to mbox ,
+.Ar deliver to maildir ,
+.Ar deliver to mda ,
+.Ar relay via )
+may use format specifiers which will be expanded before delivery or
+relaying. The following formats are currently supported:
+%a expands to the user part of the email address prior to the
+resolution of aliases, %u expands to the user part after aliases
+resolution and will typically be the system account, %d expands to
+the domain part of the email address.
+.Sh EXAMPLES
+The following example configures a machine to accept local delivery
+for both localhost and example.com, as well as the relaying of mail
+destined for example.org through the mx1.example.org server and mail
+destined for example.net through regular MX records lookup:
+.Pp
+.Bd -literal -offset listen
+.Pp
+listen on 0.0.0.0 port 25
+accept for domain "localhost" deliver to mbox "/var/mail/%u"
+accept for domain "example.com" deliver to maildir "/home/%u/Maildir"
+accept for domain "example.org" relay via "mx1.example.org"
+accept for domain "example.net" relay
+.Ed
+.Sh FILES
+.Bl -tag -width "/var/mail" -compact
+.It Pa /var/spool/smtpd/
+Spool directories for mail during processing.
+.It Pa /etc/mail/smtpd.conf
+Default location for this file.
+.El
+.Sh SEE ALSO
+.Xr smtpd 8 ,
+.Xr smtpctl 8 ,
+.Xr smtpdb 8
+.Sh HISTORY
+.Xr smtpd 8
+first appeared in
+.Ox 4.4 .
diff --git a/usr.sbin/smtpd/smtpd.h b/usr.sbin/smtpd/smtpd.h
new file mode 100644
index 00000000000..6fa1af2d741
--- /dev/null
+++ b/usr.sbin/smtpd/smtpd.h
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2008 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.
+ */
+
+#define CONF_FILE "/etc/mail/smtpd.conf"
+#define MAX_LISTEN 16
+#define STRLEN 1024
+#define PROC_COUNT 8
+#define READ_BUF_SIZE 65535
+#define MAX_NAME_SIZE 64
+
+/* sizes include the tailing '\0' */
+#define MAX_LOCALPART_SIZE 65
+#define MAX_DOMAINPART_SIZE MAXHOSTNAMELEN
+#define MAX_PATH_SIZE 256
+
+/*#define SMTPD_CONNECT_TIMEOUT (60)*/
+#define SMTPD_CONNECT_TIMEOUT (10)
+#define SMTPD_QUEUE_INTERVAL (15 * 60)
+#define SMTPD_QUEUE_MAXINTERVAL (4 * 60 * 60)
+#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60)
+#define SMTPD_USER "_smtpd"
+#define SMTPD_SOCKET "/var/run/smtpd.sock"
+#define SMTPD_BANNER "220 %s OpenSMTPD\r\n"
+#define SMTPD_SESSION_TIMEOUT 300
+
+#define RCPTBUFSZ 256
+
+#define PATH_SPOOL "/var/spool/smtpd"
+
+#define PATH_MESSAGES "/messages"
+#define PATH_LOCAL "/local"
+#define PATH_RELAY "/relay"
+#define PATH_DAEMON "/daemon"
+#define PATH_ENVELOPES "/envelopes"
+
+/* used by newaliases */
+#define PATH_ALIASES "/etc/mail/aliases"
+#define PATH_ALIASESDB "/etc/mail/aliases.db"
+
+/* number of MX records to lookup */
+#define MXARRAYSIZE 5
+
+struct address {
+ char hostname[MAXHOSTNAMELEN];
+ u_int16_t port;
+};
+
+struct netaddr {
+ struct sockaddr_storage ss;
+ int masked;
+};
+
+/* buffer specific headers */
+struct buf {
+ TAILQ_ENTRY(buf) entry;
+ u_char *buf;
+ size_t size;
+ size_t max;
+ size_t wpos;
+ size_t rpos;
+ int fd;
+};
+
+struct msgbuf {
+ TAILQ_HEAD(, buf) bufs;
+ u_int32_t queued;
+ int fd;
+};
+
+struct buf_read {
+ u_char buf[READ_BUF_SIZE];
+ u_char *rptr;
+ size_t wpos;
+};
+
+struct imsg_fd {
+ TAILQ_ENTRY(imsg_fd) entry;
+ int fd;
+ u_int32_t id;
+};
+
+struct imsgbuf {
+ TAILQ_HEAD(, imsg_fd) fds;
+ struct buf_read r;
+ struct msgbuf w;
+ struct event ev;
+ void (*handler)(int, short, void *);
+ int fd;
+ pid_t pid;
+ short events;
+ void *data;
+ u_int32_t id;
+};
+
+struct imsg_hdr {
+ u_int16_t type;
+ u_int16_t len;
+ u_int32_t peerid;
+ pid_t pid;
+};
+
+struct imsg {
+ struct imsg_hdr hdr;
+ u_int32_t id;
+ void *data;
+};
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK, /* answer to smtpctl requests */
+ IMSG_CTL_FAIL,
+ IMSG_CTL_SHUTDOWN,
+ IMSG_CONF_START,
+ IMSG_CONF_SSL,
+ IMSG_CONF_SSL_CERT,
+ IMSG_CONF_SSL_KEY,
+ IMSG_CONF_LISTENER,
+ IMSG_CONF_MAP,
+ IMSG_CONF_RULE,
+ IMSG_CONF_CONDITION,
+ IMSG_CONF_OPTION,
+ IMSG_CONF_END,
+ IMSG_CONF_RELOAD,
+ IMSG_LKA_LOOKUP_MAIL,
+ IMSG_LKA_LOOKUP_RCPT,
+ IMSG_LKA_ALIAS_LOOKUP,
+ IMSG_LKA_VUSER_LOOKUP,
+ IMSG_LKA_ALIAS_RESULT,
+ IMSG_LKA_VUSER_RESULT,
+ IMSG_LKA_ALIAS_RESULT_ACK,
+ IMSG_LKA_ALIAS_SCHEDULE,
+ IMSG_LKA_ALIAS_END,
+ IMSG_LKA_NO_ALIAS,
+ IMSG_LKA_MX_LOOKUP,
+ IMSG_LKA_FORWARD_LOOKUP,
+ IMSG_LKA_HOSTNAME_LOOKUP,
+ IMSG_MDA_MAILBOX_FILE,
+ IMSG_MDA_MESSAGE_FILE,
+ IMSG_MDA_MAILBOX_FILE_ERROR,
+ IMSG_MDA_MESSAGE_FILE_ERROR,
+ IMSG_MFA_RPATH_SUBMIT,
+ IMSG_MFA_RCPT_SUBMIT,
+ IMSG_MFA_DATA_SUBMIT,
+ IMSG_MFA_LOOKUP_MAIL,
+ IMSG_MFA_LOOKUP_RCPT,
+ IMSG_QUEUE_REMOVE_SUBMISSION,
+ IMSG_QUEUE_CREATE_MESSAGE_FILE,
+ IMSG_QUEUE_DELETE_MESSAGE_FILE,
+ IMSG_QUEUE_MESSAGE_SUBMIT,
+ IMSG_QUEUE_MESSAGE_UPDATE,
+ IMSG_QUEUE_BATCH_COMPLETE,
+ IMSG_QUEUE_BATCH_CLOSE,
+ IMSG_QUEUE_MESSAGE_FD,
+
+ IMSG_QUEUE_ACCEPTED_CLOSE,
+ IMSG_QUEUE_RETRY_CLOSE,
+ IMSG_QUEUE_REJECTED_CLOSE,
+
+ IMSG_QUEUE_RECIPIENT_ACCEPTED,
+ IMSG_QUEUE_RECIPIENT_UPDATED,
+
+ IMSG_CREATE_BATCH,
+ IMSG_BATCH_APPEND,
+ IMSG_BATCH_CLOSE,
+
+ IMSG_SMTP_MESSAGE_FILE,
+ IMSG_SMTP_SUBMIT_ACK,
+ IMSG_SMTP_HOSTNAME_ANSWER,
+ IMSG_PARENT_MAILBOX_OPEN,
+ IMSG_PARENT_MESSAGE_OPEN,
+ IMSG_PARENT_MAILBOX_RENAME,
+
+ IMSG_PARENT_AUTHENTICATE
+};
+
+#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE 16384
+
+enum blockmodes {
+ BM_NORMAL,
+ BM_NONBLOCK
+};
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ u_int8_t flags;
+#define CTL_CONN_NOTIFY 0x01
+ struct imsgbuf ibuf;
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+typedef u_int32_t objid_t;
+
+struct ctl_id {
+ objid_t id;
+ char name[MAX_NAME_SIZE];
+};
+
+enum smtp_proc_type {
+ PROC_PARENT = 0,
+ PROC_SMTP,
+ PROC_MFA,
+ PROC_LKA,
+ PROC_QUEUE,
+ PROC_MDA,
+ PROC_MTA,
+ PROC_CONTROL,
+} smtpd_process;
+
+struct peer {
+ enum smtp_proc_type id;
+ void (*cb)(int, short, void *);
+};
+
+enum map_type {
+ T_SINGLE,
+ T_LIST,
+ T_HASH
+};
+
+enum map_src {
+ S_NONE,
+ S_DYN,
+ S_DNS,
+ S_FILE,
+ S_DB,
+ S_EXT
+};
+
+enum mapel_type {
+ ME_STRING,
+ ME_NET,
+ ME_NETMASK
+};
+
+struct mapel {
+ TAILQ_ENTRY(mapel) me_entry;
+ union mapel_data {
+ char med_string[STRLEN];
+ struct netaddr med_addr;
+ } me_key;
+ union mapel_data me_val;
+};
+
+struct map {
+ TAILQ_ENTRY(map) m_entry;
+#define F_USED 0x01
+#define F_DYNAMIC 0x02
+ u_int8_t m_flags;
+ char m_name[STRLEN];
+ objid_t m_id;
+ enum map_type m_type;
+ enum mapel_type m_eltype;
+ enum map_src m_src;
+ char m_config[MAXPATHLEN];
+ TAILQ_HEAD(mapel_list, mapel) m_contents;
+};
+
+enum cond_type {
+ C_ALL,
+ C_NET,
+ C_DOM
+};
+
+struct cond {
+ TAILQ_ENTRY(cond) c_entry;
+ objid_t c_map;
+ enum cond_type c_type;
+ struct map *c_match;
+};
+
+enum opt_type {
+ O_RWUSER, /* rewrite user */
+ O_RWDOMAIN, /* rewrite domain */
+};
+
+struct opt {
+ TAILQ_ENTRY(opt) o_entry;
+ enum opt_type o_type;
+};
+
+enum action_type {
+ A_RELAY,
+ A_RELAYVIA,
+ A_MAILDIR,
+ A_MBOX,
+ A_FILENAME,
+ A_EXT
+};
+#define IS_MAILBOX(x) ((x) == A_MAILDIR || (x) == A_MBOX || (x) == A_FILENAME)
+#define IS_RELAY(x) ((x) == A_RELAY || (x) == A_RELAYVIA)
+#define IS_EXT(x) ((x) == A_EXT)
+
+struct rule {
+ TAILQ_ENTRY(rule) r_entry;
+ int r_accept;
+ struct map *r_sources;
+ TAILQ_HEAD(condlist, cond) r_conditions;
+ enum action_type r_action;
+ union {
+ char path[MAXPATHLEN];
+ struct address host;
+#define MAXCOMMANDLEN 256
+ char command[MAXCOMMANDLEN];
+ } r_value;
+ TAILQ_HEAD(optlist, opt) r_options;
+};
+
+enum path_flags {
+ F_ALIAS = 0x1,
+ F_VIRTUAL = 0x2,
+ F_EXPANDED = 0x4,
+ F_NOFORWARD = 0x8,
+ F_FORWARDED = 0x10
+};
+
+struct path {
+ TAILQ_ENTRY(path) entry;
+ struct rule rule;
+ enum path_flags flags;
+ u_int8_t forwardcnt;
+ char user[MAX_LOCALPART_SIZE];
+ char domain[MAX_DOMAINPART_SIZE];
+ char pw_name[MAXLOGNAME];
+ union {
+ char filename[MAXPATHLEN];
+ char filter[MAXPATHLEN];
+ } u;
+};
+
+enum alias_type {
+ ALIAS_USERNAME,
+ ALIAS_FILENAME,
+ ALIAS_FILTER,
+ ALIAS_INCLUDE,
+ ALIAS_ADDRESS
+};
+
+struct alias {
+ TAILQ_ENTRY(alias) entry;
+ enum alias_type type;
+ union {
+ char username[MAXLOGNAME];
+ char filename[MAXPATHLEN];
+ char filter[MAXPATHLEN];
+ struct path path;
+ } u;
+};
+TAILQ_HEAD(aliaseslist, alias);
+
+struct submit_status {
+ u_int64_t id;
+ int code;
+ union {
+ struct path path;
+ char msgid[MAXPATHLEN];
+ char errormsg[STRLEN];
+ } u;
+ struct sockaddr_storage ss;
+};
+
+struct message_recipient {
+ u_int64_t id;
+ struct sockaddr_storage ss;
+ struct path path;
+};
+
+enum message_type {
+ T_MDA_MESSAGE = 0x1,
+ T_MTA_MESSAGE = 0x2,
+ T_DAEMON_MESSAGE = 0x4
+};
+
+enum message_status {
+ S_MESSAGE_PERMFAILURE = 0x1,
+ S_MESSAGE_TEMPFAILURE = 0x2,
+ S_MESSAGE_REJECTED = 0x4,
+ S_MESSAGE_ACCEPTED = 0x8,
+ S_MESSAGE_RETRY = 0x10,
+ S_MESSAGE_EDNS = 0x20,
+ S_MESSAGE_ECONNECT = 0x40
+};
+
+enum message_flags {
+ F_MESSAGE_COMPLETE = 0x1,
+ F_MESSAGE_RESOLVED = 0x2,
+ F_MESSAGE_READY = 0x4,
+ F_MESSAGE_EXPIRED = 0x8,
+ F_MESSAGE_PROCESSING = 0x10
+};
+
+struct message {
+ SPLAY_ENTRY(message) nodes;
+ TAILQ_ENTRY(message) entry;
+
+ enum message_type type;
+
+ u_int64_t id;
+ u_int64_t session_id;
+ u_int64_t batch_id;
+
+ char message_id[MAXPATHLEN];
+ char message_uid[MAXPATHLEN];
+
+ char session_helo[MAXHOSTNAMELEN];
+ char session_hostname[MAXHOSTNAMELEN];
+ char session_errorline[STRLEN];
+ struct sockaddr_storage session_ss;
+
+ struct path sender;
+ struct path recipient;
+ TAILQ_HEAD(pathlist,path) recipients;
+
+ u_int16_t rcptcount;
+
+ time_t creation;
+ time_t lasttry;
+ u_int8_t retry;
+ enum message_flags flags;
+ enum message_status status;
+ FILE *datafp;
+ int mboxfd;
+ int messagefd;
+};
+
+enum batch_status {
+ S_BATCH_PERMFAILURE = 0x1,
+ S_BATCH_TEMPFAILURE = 0x2,
+ S_BATCH_REJECTED = 0x4,
+ S_BATCH_ACCEPTED = 0x8,
+ S_BATCH_RETRY = 0x10,
+ S_BATCH_EDNS = 0x20,
+ S_BATCH_ECONNECT = 0x40
+};
+
+enum batch_type {
+ T_MDA_BATCH = 0x1,
+ T_MTA_BATCH = 0x2,
+ T_DAEMON_BATCH = 0x4
+};
+
+enum batch_flags {
+ F_BATCH_COMPLETE = 0x1,
+ F_BATCH_RESOLVED = 0x2,
+ F_BATCH_SCHEDULED = 0x4,
+ F_BATCH_EXPIRED = 0x8,
+};
+
+struct mdaproc {
+ SPLAY_ENTRY(mdaproc) mdaproc_nodes;
+
+ pid_t pid;
+};
+
+struct batch {
+ SPLAY_ENTRY(batch) b_nodes;
+
+ u_int64_t id;
+ enum batch_type type;
+ enum batch_flags flags;
+
+ struct rule rule;
+
+ struct event ev;
+ struct timeval tv;
+ int peerfd;
+ struct bufferevent *bev;
+ u_int8_t state;
+ struct smtpd *env;
+
+ char message_id[MAXPATHLEN];
+ char hostname[MAXHOSTNAMELEN];
+ char errorline[STRLEN];
+
+ u_int8_t h_errno;
+ struct sockaddr_storage ss[MXARRAYSIZE*2];
+ u_int8_t ss_cnt;
+ u_int8_t ss_off;
+
+ time_t creation;
+ time_t lasttry;
+ u_int8_t retry;
+
+ struct message message;
+ struct message *messagep;
+ FILE *messagefp;
+ TAILQ_HEAD(messagelist, message) messages;
+
+ enum batch_status status;
+};
+
+enum session_state {
+ S_INIT = 0,
+ S_GREETED,
+ S_TLS,
+ S_AUTH,
+ S_HELO,
+ S_MAIL,
+ S_RCPT,
+ S_DATA,
+ S_DATACONTENT,
+ S_DONE,
+ S_QUIT
+};
+
+struct ssl {
+ SPLAY_ENTRY(ssl) ssl_nodes;
+ char ssl_name[PATH_MAX];
+ char *ssl_cert;
+ off_t ssl_cert_len;
+ char *ssl_key;
+ off_t ssl_key_len;
+};
+
+struct listener {
+#define F_STARTTLS 0x01
+#define F_SSMTP 0x02
+#define F_SSL (F_SSMTP|F_STARTTLS)
+ u_int8_t flags;
+ int fd;
+ struct sockaddr_storage ss;
+ in_port_t port;
+ int backlog;
+ struct timeval timeout;
+ struct event ev;
+ struct smtpd *env;
+ char ssl_cert_name[PATH_MAX];
+ struct ssl *ssl;
+ void *ssl_ctx;
+ TAILQ_ENTRY(listener) entry;
+};
+
+struct session_auth_req {
+ u_int64_t session_id;
+ char buffer[STRLEN];
+};
+
+struct session_auth_reply {
+ u_int64_t session_id;
+ u_int8_t value;
+};
+
+enum session_flags {
+ F_QUIT = 0x1,
+ F_IMSG_SENT = 0x2,
+ F_8BITMIME = 0x4,
+ F_SECURE = 0x8,
+ F_AUTHENTICATED = 0x10
+};
+
+struct session {
+ SPLAY_ENTRY(session) s_nodes;
+ u_int64_t s_id;
+
+ enum session_flags s_flags;
+ enum session_state s_state;
+ time_t s_tm;
+ int s_fd;
+ struct sockaddr_storage s_ss;
+ char s_hostname[MAXHOSTNAMELEN];
+ struct event s_ev;
+ struct bufferevent *s_bev;
+ struct listener *s_l;
+ struct smtpd *s_env;
+ void *s_ssl;
+ u_char *s_buf;
+ int s_buflen;
+ struct timeval s_tv;
+ struct message s_msg;
+};
+
+struct smtpd {
+#define SMTPD_OPT_VERBOSE 0x00000001
+#define SMTPD_OPT_NOACTION 0x00000002
+ u_int32_t sc_opts;
+#define SMTPD_CONFIGURING 0x00000001
+#define SMTPD_EXITING 0x00000002
+ u_int32_t sc_flags;
+ struct timeval sc_qintval;
+ struct event sc_ev;
+ int sc_pipes[PROC_COUNT]
+ [PROC_COUNT][2];
+ struct imsgbuf *sc_ibufs[PROC_COUNT];
+ struct passwd *sc_pw;
+ char sc_hostname[MAXHOSTNAMELEN];
+ TAILQ_HEAD(listenerlist, listener) sc_listeners;
+ TAILQ_HEAD(maplist, map) *sc_maps;
+ TAILQ_HEAD(rulelist, rule) *sc_rules;
+ SPLAY_HEAD(sessiontree, session) sc_sessions;
+ SPLAY_HEAD(msgtree, message) sc_messages;
+ SPLAY_HEAD(ssltree, ssl) sc_ssl;
+
+ SPLAY_HEAD(batchtree, batch) batch_queue;
+ SPLAY_HEAD(mdaproctree, mdaproc) mdaproc_queue;
+};
+
+/* aliases.c */
+int is_alias(struct path *);
+
+/* atomic.c */
+ssize_t atomic_read(int, void *, size_t);
+ssize_t atomic_write(int, const void *, size_t);
+ssize_t atomic_printfd(int, const char *, ...);
+
+/* log.c */
+void log_init(int);
+void log_warn(const char *, ...);
+void log_warnx(const char *, ...);
+void log_info(const char *, ...);
+void log_debug(const char *, ...);
+__dead void fatal(const char *);
+__dead void fatalx(const char *);
+
+
+/* buffer.c */
+struct buf *buf_open(size_t);
+struct buf *buf_dynamic(size_t, size_t);
+int buf_add(struct buf *, void *, size_t);
+void *buf_reserve(struct buf *, size_t);
+int buf_close(struct msgbuf *, struct buf *);
+void buf_free(struct buf *);
+void msgbuf_init(struct msgbuf *);
+void msgbuf_clear(struct msgbuf *);
+int msgbuf_write(struct msgbuf *);
+
+/* imsg.c */
+void imsg_init(struct imsgbuf *, int, void (*)(int, short, void *));
+ssize_t imsg_read(struct imsgbuf *);
+ssize_t imsg_get(struct imsgbuf *, struct imsg *);
+int imsg_compose(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ int, void *, u_int16_t);
+int imsg_composev(struct imsgbuf *, enum imsg_type, u_int32_t,
+ pid_t, int, const struct iovec *, int);
+int imsg_compose_fds(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ void *, u_int16_t, int, ...);
+struct buf *imsg_create(struct imsgbuf *, enum imsg_type, u_int32_t, pid_t,
+ u_int16_t);
+int imsg_add(struct buf *, void *, u_int16_t);
+int imsg_append(struct imsgbuf *, struct buf *);
+int imsg_close(struct imsgbuf *, struct buf *);
+void imsg_free(struct imsg *);
+void imsg_event_add(struct imsgbuf *); /* needs to be provided externally */
+int imsg_get_fd(struct imsgbuf *, struct imsg *);
+int imsg_flush(struct imsgbuf *);
+void imsg_clear(struct imsgbuf *);
+
+/* lka.c */
+pid_t lka(struct smtpd *);
+
+/* mfa.c */
+pid_t mfa(struct smtpd *);
+int msg_cmp(struct message *, struct message *);
+SPLAY_PROTOTYPE(msgtree, message, nodes, msg_cmp);
+
+/* queue.c */
+pid_t queue(struct smtpd *);
+u_int64_t queue_generate_id(void);
+int batch_cmp(struct batch *, struct batch *);
+struct batch *batch_by_id(struct smtpd *, u_int64_t);
+struct message *message_by_id(struct smtpd *, struct batch *, u_int64_t);
+int queue_remove_batch_message(struct smtpd *, struct batch *, struct message *);
+SPLAY_PROTOTYPE(batchtree, batch, b_nodes, batch_cmp);
+
+/* mda.c */
+pid_t mda(struct smtpd *);
+int mdaproc_cmp(struct mdaproc *, struct mdaproc *);
+SPLAY_PROTOTYPE(mdaproctree, mdaproc, mdaproc_nodes, mdaproc_cmp);
+
+/* mta.c */
+pid_t mta(struct smtpd *);
+
+/* control.c */
+pid_t control(struct smtpd *);
+void session_socket_blockmode(int, enum blockmodes);
+
+/* smtp.c */
+pid_t smtp(struct smtpd *);
+void smtp_listener_setup(struct smtpd *, struct listener *);
+
+/* smtp_session.c */
+void session_init(struct listener *, struct session *);
+void session_read(struct bufferevent *, void *);
+void session_write(struct bufferevent *, void *);
+void session_error(struct bufferevent *, short, void *);
+int session_cmp(struct session *, struct session *);
+void session_msg_submit(struct session *);
+void session_pickup(struct session *, struct submit_status *);
+void session_destroy(struct session *);
+SPLAY_PROTOTYPE(sessiontree, session, s_nodes, session_cmp);
+
+/* store.c */
+int store_write_header(struct batch *, struct message *);
+int store_write_message(struct batch *, struct message *);
+int store_write_daemon(struct batch *, struct message *);
+int store_message(struct batch *, struct message *,
+ int (*)(struct batch *, struct message *));
+
+/* config.c */
+#define PURGE_LISTENERS 0x01
+#define PURGE_MAPS 0x02
+#define PURGE_RULES 0x04
+#define PURGE_SSL 0x08
+#define PURGE_EVERYTHING 0xff
+void purge_config(struct smtpd *, u_int8_t);
+void unconfigure(struct smtpd *);
+void configure(struct smtpd *);
+void init_peers(struct smtpd *);
+void config_peers(struct smtpd *, struct peer *, u_int);
+
+/* parse.y */
+int parse_config(struct smtpd *, const char *, int);
+int cmdline_symset(char *);
+
+/* ssl.c */
+void ssl_init(void);
+void ssl_transaction(struct session *);
+
+void ssl_session_init(struct session *);
+void ssl_session_destroy(struct session *);
+int ssl_load_certfile(struct smtpd *, const char *);
+void ssl_setup(struct smtpd *, struct listener *);
+int ssl_cmp(struct ssl *, struct ssl *);
+SPLAY_PROTOTYPE(ssltree, ssl, ssl_nodes, ssl_cmp);
+
+/* ssl_privsep.c */
+int ssl_ctx_use_private_key(void *, char *, off_t);
+int ssl_ctx_use_certificate_chain(void *, char *, off_t);
+
+/* smtpd.c */
+struct map *map_find(struct smtpd *, objid_t);
+struct map *map_findbyname(struct smtpd *, const char *);
diff --git a/usr.sbin/smtpd/ssl.c b/usr.sbin/smtpd/ssl.c
new file mode 100644
index 00000000000..28a5284d250
--- /dev/null
+++ b/usr.sbin/smtpd/ssl.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2008 Reyk Floeter <reyk@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/tree.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <ctype.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+#include <openssl/engine.h>
+#include <openssl/err.h>
+
+#include "smtpd.h"
+
+#define SSL_CIPHERS "HIGH:!ADH"
+
+void ssl_error(const char *);
+char *ssl_load_file(const char *, off_t *);
+SSL_CTX *ssl_ctx_create(void);
+void ssl_session_accept(int, short, void *);
+void ssl_read(int, short, void *);
+void ssl_write(int, short, void *);
+int ssl_bufferevent_add(struct event *, int);
+
+extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+ size_t, void *);
+
+void
+ssl_read(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct session *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+ size_t len;
+ char rbuf[READ_BUF_SIZE];
+ int howmuch = READ_BUF_SIZE;
+
+ what = EVBUFFER_READ;
+ ret = ssl_err = 0;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (bufev->wm_read.high != 0)
+ howmuch = MIN(sizeof(rbuf), bufev->wm_read.high);
+
+ ret = SSL_read(s->s_ssl, rbuf, howmuch);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_read");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+
+ if (evbuffer_add(bufev->input, rbuf, ret) == -1) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+ len = EVBUFFER_LENGTH(bufev->input);
+ if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+ return;
+ if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+ struct evbuffer *buf = bufev->input;
+ event_del(&bufev->ev_read);
+ evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+ return;
+ }
+
+ if (bufev->readcb != NULL)
+ (*bufev->readcb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ ssl_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+ return;
+
+err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+
+void
+ssl_write(int fd, short event, void *p)
+{
+ struct bufferevent *bufev = p;
+ struct session *s = bufev->cbarg;
+ int ret;
+ int ssl_err;
+ short what;
+
+ ret = 0;
+ what = EVBUFFER_WRITE;
+
+ if (event == EV_TIMEOUT) {
+ what |= EV_TIMEOUT;
+ goto err;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output)) {
+ if (s->s_buf == NULL) {
+ s->s_buflen = EVBUFFER_LENGTH(bufev->output);
+ if ((s->s_buf = malloc(s->s_buflen)) == NULL) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ memcpy(s->s_buf, EVBUFFER_DATA(bufev->output),
+ s->s_buflen);
+ }
+
+ ret = SSL_write(s->s_ssl, s->s_buf, s->s_buflen);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ goto retry;
+ default:
+ if (ret == 0)
+ what |= EVBUFFER_EOF;
+ else {
+ ssl_error("ssl_write");
+ what |= EVBUFFER_ERROR;
+ }
+ goto err;
+ }
+ }
+ evbuffer_drain(bufev->output, ret);
+ }
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ if (EVBUFFER_LENGTH(bufev->output) != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+ if (bufev->writecb != NULL &&
+ EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+ (*bufev->writecb)(bufev, bufev->cbarg);
+ return;
+
+retry:
+ if (s->s_buflen != 0)
+ ssl_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+ return;
+
+err:
+ if (s->s_buf != NULL) {
+ free(s->s_buf);
+ s->s_buf = NULL;
+ s->s_buflen = 0;
+ }
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+int
+ssl_bufferevent_add(struct event *ev, int timeout)
+{
+ struct timeval tv;
+ struct timeval *ptv = NULL;
+
+ if (timeout) {
+ timerclear(&tv);
+ tv.tv_sec = timeout;
+ ptv = &tv;
+ }
+
+ return (event_add(ev, ptv));
+}
+
+int
+ssl_cmp(struct ssl *s1, struct ssl *s2)
+{
+ return (strcmp(s1->ssl_name, s2->ssl_name));
+}
+
+SPLAY_GENERATE(ssltree, ssl, ssl_nodes, ssl_cmp);
+
+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 + 1;
+ return (buf);
+
+fail:
+ if (buf != NULL)
+ free(buf);
+ close(fd);
+ return (NULL);
+}
+
+SSL_CTX *
+ssl_ctx_create(void)
+{
+ SSL_CTX *ctx;
+
+ ctx = SSL_CTX_new(SSLv23_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, SMTPD_SESSION_TIMEOUT);
+ SSL_CTX_set_options(ctx, SSL_OP_ALL);
+ SSL_CTX_set_options(ctx,
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+ if (!SSL_CTX_set_cipher_list(ctx, SSL_CIPHERS)) {
+ ssl_error("ssl_ctx_create");
+ fatal("ssl_ctx_create: could not set cipher list");
+ }
+ return (ctx);
+}
+
+int
+ssl_load_certfile(struct smtpd *env, const char *name)
+{
+ struct ssl *s;
+ struct ssl key;
+ char certfile[PATH_MAX];
+
+ if (strlcpy(key.ssl_name, name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name)) {
+ log_warn("ssl_load_certfile: certificate name truncated");
+ return -1;
+ }
+
+ s = SPLAY_FIND(ssltree, &env->sc_ssl, &key);
+ if (s != NULL)
+ return 0;
+
+ if ((s = calloc(1, sizeof(*s))) == NULL)
+ fatal(NULL);
+
+ (void)strlcpy(s->ssl_name, key.ssl_name, sizeof(s->ssl_name));
+
+ if (snprintf(certfile, sizeof(certfile),
+ "/etc/mail/certs/%s.crt", name) == -1) {
+ free(s);
+ return (-1);
+ }
+
+ if ((s->ssl_cert = ssl_load_file(certfile, &s->ssl_cert_len)) == NULL) {
+ free(s);
+ return (-1);
+ }
+
+ if (snprintf(certfile, sizeof(certfile),
+ "/etc/mail/certs/%s.key", name) == -1) {
+ free(s->ssl_cert);
+ free(s);
+ return -1;
+ }
+
+ if ((s->ssl_key = ssl_load_file(certfile, &s->ssl_key_len)) == NULL) {
+ free(s->ssl_cert);
+ free(s);
+ return (-1);
+ }
+
+ if (s->ssl_cert == NULL || s->ssl_key == NULL)
+ fatal("invalid certificates");
+
+ SPLAY_INSERT(ssltree, &env->sc_ssl, s);
+
+ return (0);
+}
+
+void
+ssl_init(void)
+{
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ OpenSSL_add_all_algorithms();
+
+ /* Init hardware crypto engines. */
+ ENGINE_load_builtin_engines();
+ ENGINE_register_all_complete();
+}
+
+void
+ssl_setup(struct smtpd *env, struct listener *l)
+{
+ struct ssl key;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ if (strlcpy(key.ssl_name, l->ssl_cert_name, sizeof(key.ssl_name))
+ >= sizeof(key.ssl_name))
+ fatal("ssl_setup: certificate name truncated");
+
+ if ((l->ssl = SPLAY_FIND(ssltree, &env->sc_ssl, &key)) == NULL)
+ fatal("ssl_setup: certificate tree corrupted");
+
+ l->ssl_ctx = ssl_ctx_create();
+
+ if (!ssl_ctx_use_certificate_chain(l->ssl_ctx,
+ l->ssl->ssl_cert, l->ssl->ssl_cert_len))
+ goto err;
+ if (!ssl_ctx_use_private_key(l->ssl_ctx,
+ l->ssl->ssl_key, l->ssl->ssl_key_len))
+ goto err;
+
+ if (!SSL_CTX_check_private_key(l->ssl_ctx))
+ goto err;
+ if (!SSL_CTX_set_session_id_context(l->ssl_ctx,
+ (const unsigned char *)l->ssl_cert_name, strlen(l->ssl_cert_name) + 1))
+ goto err;
+
+ log_debug("ssl_setup: ssl setup finished for listener: %p", l);
+ return;
+
+err:
+ if (l->ssl_ctx != NULL)
+ SSL_CTX_free(l->ssl_ctx);
+ ssl_error("ssl_setup");
+ fatal("ssl_setup: cannot set SSL up");
+ return;
+}
+
+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));
+ log_debug("SSL library error: %s: %s", where, errbuf);
+ }
+}
+
+void
+ssl_session_accept(int fd, short event, void *p)
+{
+ struct session *s = p;
+ int ret;
+ int retry_flag;
+ int ssl_err;
+
+ if (event == EV_TIMEOUT) {
+ log_debug("ssl_session_accept: session timed out");
+ session_destroy(s);
+ return;
+ }
+
+ retry_flag = ssl_err = 0;
+
+ log_debug("ssl_session_accept: accepting client");
+ ret = SSL_accept(s->s_ssl);
+ if (ret <= 0) {
+ ssl_err = SSL_get_error(s->s_ssl, ret);
+
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ retry_flag = EV_READ;
+ goto retry;
+ case SSL_ERROR_WANT_WRITE:
+ retry_flag = EV_WRITE;
+ goto retry;
+ case SSL_ERROR_ZERO_RETURN:
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ session_destroy(s);
+ return;
+ }
+ /* FALLTHROUGH */
+ default:
+ ssl_error("ssl_session_accept");
+ session_destroy(s);
+ return;
+ }
+ }
+
+ event_set(&s->s_bev->ev_read, s->s_fd, EV_READ, ssl_read, s->s_bev);
+ event_set(&s->s_bev->ev_write, s->s_fd, EV_WRITE, ssl_write, s->s_bev);
+
+ log_info("ssl_session_accept: accepted ssl client");
+ s->s_flags |= F_SECURE;
+ session_pickup(s, NULL);
+ return;
+retry:
+ event_add(&s->s_ev, &s->s_tv);
+}
+
+void
+ssl_session_init(struct session *s)
+{
+ struct listener *l;
+ SSL *ssl;
+
+ l = s->s_l;
+
+ if (!(l->flags & F_SSL))
+ return;
+
+ log_debug("ssl_session_init: switching to SSL");
+ ssl = SSL_new(l->ssl_ctx);
+ if (ssl == NULL)
+ goto err;
+
+ if (!SSL_set_ssl_method(ssl, SSLv23_server_method()))
+ goto err;
+ if (!SSL_set_fd(ssl, s->s_fd))
+ goto err;
+ SSL_set_accept_state(ssl);
+
+ s->s_ssl = ssl;
+
+ s->s_tv.tv_sec = SMTPD_SESSION_TIMEOUT;
+ s->s_tv.tv_usec = 0;
+ event_set(&s->s_ev, s->s_fd, EV_READ|EV_TIMEOUT, ssl_session_accept, s);
+ event_add(&s->s_ev, &s->s_tv);
+ return;
+
+ err:
+ if (ssl != NULL)
+ SSL_free(ssl);
+ ssl_error("ssl_session_init");
+}
+
+void
+ssl_session_destroy(struct session *s)
+{
+ SSL_free(s->s_ssl);
+}
diff --git a/usr.sbin/smtpd/ssl_privsep.c b/usr.sbin/smtpd/ssl_privsep.c
new file mode 100644
index 00000000000..e2f90b794fa
--- /dev/null
+++ b/usr.sbin/smtpd/ssl_privsep.c
@@ -0,0 +1,170 @@
+/* $OpenBSD: ssl_privsep.c,v 1.1 2008/11/01 21:35:28 gilles 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 <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_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);
+}
diff --git a/usr.sbin/smtpd/store.c b/usr.sbin/smtpd/store.c
new file mode 100644
index 00000000000..a67e4e4b1fd
--- /dev/null
+++ b/usr.sbin/smtpd/store.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@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/stat.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <util.h>
+#include <unistd.h>
+
+#include "smtpd.h"
+
+int fd_copy(int, int, size_t);
+int fd_append(int, int);
+
+int
+fd_copy(int dest, int src, size_t len)
+{
+ char buffer[8192];
+ size_t rlen;
+
+ for (; len;) {
+
+ rlen = len < sizeof(buffer) ? len : sizeof(buffer);
+ if (atomic_read(src, buffer, rlen) == -1)
+ return 0;
+
+ if (atomic_write(dest, buffer, rlen) == -1)
+ return 0;
+
+ len -= rlen;
+ }
+
+ return 1;
+}
+
+int
+fd_append(int dest, int src)
+{
+ struct stat sb;
+ size_t srcsz;
+
+ if (fstat(src, &sb) == -1)
+ return 0;
+ srcsz = sb.st_size;
+
+ if (! fd_copy(dest, src, srcsz))
+ return 0;
+
+ return 1;
+}
+
+int
+store_write_header(struct batch *batchp, struct message *messagep)
+{
+ time_t tm;
+ char timebuf[26]; /* current time */
+ char ctimebuf[26]; /* creation time */
+ void *p;
+ char addrbuf[INET6_ADDRSTRLEN];
+
+ tm = time(NULL);
+ ctime_r(&tm, timebuf);
+ timebuf[strcspn(timebuf, "\n")] = '\0';
+
+ tm = time(&messagep->creation);
+ ctime_r(&tm, ctimebuf);
+ ctimebuf[strcspn(ctimebuf, "\n")] = '\0';
+
+ if (messagep->session_ss.ss_family == PF_INET) {
+ struct sockaddr_in *ssin = (struct sockaddr_in *)&messagep->session_ss;
+ p = &ssin->sin_addr.s_addr;
+ }
+ if (messagep->session_ss.ss_family == PF_INET6) {
+ struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&messagep->session_ss;
+ p = &ssin6->sin6_addr.s6_addr;
+ }
+
+ bzero(addrbuf, sizeof (addrbuf));
+ inet_ntop(messagep->session_ss.ss_family, p, addrbuf, sizeof (addrbuf));
+
+ if (batchp->type & T_DAEMON_BATCH) {
+ if (atomic_printfd(messagep->mboxfd, "From %s@%s %s\n",
+ "MAILER-DAEMON", batchp->env->sc_hostname, timebuf) == -1)
+ return 0;
+ if (atomic_printfd(messagep->mboxfd,
+ "Received: from %s (%s [%s%s])\n"
+ "\tby %s with ESMTP id %s\n"
+ "\tfor <%s@%s>; %s\n\n",
+ messagep->session_helo, messagep->session_hostname,
+ messagep->session_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf,
+ batchp->env->sc_hostname, messagep->message_id,
+ messagep->sender.user, messagep->sender.domain, ctimebuf) == -1) {
+ return 0;
+ }
+ return 1;
+ }
+
+ if (atomic_printfd(messagep->mboxfd,
+ "From %s@%s %s\n"
+ "Received: from %s (%s [%s%s])\n"
+ "\tby %s with ESMTP id %s\n"
+ "\tfor <%s@%s>; %s\n",
+ messagep->sender.user, messagep->sender.domain, timebuf,
+ messagep->session_helo, messagep->session_hostname,
+ messagep->session_ss.ss_family == PF_INET ? "" : "IPv6:", addrbuf,
+ batchp->env->sc_hostname, batchp->message_id,
+ messagep->recipient.user, messagep->recipient.domain, ctimebuf) == -1) {
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+store_write_daemon(struct batch *batchp, struct message *messagep)
+{
+ u_int32_t i;
+ struct message *recipient;
+
+ if (! store_write_header(batchp, messagep))
+ return 0;
+
+ if (atomic_printfd(messagep->mboxfd,
+ "Hi !\n\n"
+ "This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail it is\n"
+ "just a notification to let you know that an error has occured.\n\n") == -1) {
+ return 0;
+ }
+
+ if ((batchp->status & S_BATCH_PERMFAILURE) && atomic_printfd(messagep->mboxfd,
+ "You ran into a PERMANENT FAILURE, which means that the e-mail can't\n"
+ "be delivered to the remote host no matter how much I'll try.\n\n"
+ "Diagnostic:\n"
+ "%s\n\n", batchp->errorline) == -1) {
+ return 0;
+ }
+
+ if ((batchp->status & S_BATCH_TEMPFAILURE) && atomic_printfd(messagep->mboxfd,
+ "You ran into a TEMPORARY FAILURE, which means that the e-mail can't\n"
+ "be delivered right now, but could be deliverable at a later time. I\n"
+ "will attempt until it succeeds for the next four days, then let you\n"
+ "know if it didn't work out.\n\n"
+ "Diagnostic:\n"
+ "%s\n\n", batchp->errorline) == -1) {
+ return 0;
+ }
+
+ if (! (batchp->status & S_BATCH_TEMPFAILURE)) {
+ /* First list the temporary failures */
+ i = 0;
+ TAILQ_FOREACH(recipient, &batchp->messages, entry) {
+ if (recipient->status & S_MESSAGE_TEMPFAILURE) {
+ if (i == 0) {
+ if (atomic_printfd(messagep->mboxfd,
+ "The following recipients caused a temporary failure:\n") == -1)
+ return 0;
+ ++i;
+ }
+ if (atomic_printfd(messagep->mboxfd,
+ "\t<%s@%s>:\n%s\n\n", recipient->recipient.user, recipient->recipient.domain,
+ recipient->session_errorline) == -1) {
+ return 0;
+ }
+ }
+ }
+
+ /* Then list the permanent failures */
+ i = 0;
+ TAILQ_FOREACH(recipient, &batchp->messages, entry) {
+ if (recipient->status & S_MESSAGE_PERMFAILURE) {
+ if (i == 0) {
+ if (atomic_printfd(messagep->mboxfd,
+ "The following recipients caused a permanent failure:\n") == -1)
+ return 0;
+ ++i;
+ }
+ if (atomic_printfd(messagep->mboxfd,
+ "\t<%s@%s>:\n%s\n\n", recipient->recipient.user, recipient->recipient.domain,
+ recipient->session_errorline) == -1) {
+ return 0;
+ }
+ }
+ }
+ }
+
+ if (atomic_printfd(messagep->mboxfd,
+ "Below is a copy of the original message:\n\n") == -1)
+ return 0;
+
+ if (! fd_append(messagep->mboxfd, messagep->messagefd))
+ return 0;
+
+ if (atomic_printfd(messagep->mboxfd, "\n") == -1)
+ return 0;
+
+ return 1;
+}
+
+int
+store_write_message(struct batch *batchp, struct message *messagep)
+{
+ if (! store_write_header(batchp, messagep))
+ return 0;
+
+ if (! fd_append(messagep->mboxfd, messagep->messagefd))
+ return 0;
+
+ if (atomic_printfd(messagep->mboxfd, "\n") == -1)
+ return 0;
+
+ return 1;
+}
+
+int
+store_message(struct batch *batchp, struct message *messagep,
+ int (*writer)(struct batch *, struct message *))
+{
+ struct stat sb;
+
+ if (fstat(messagep->mboxfd, &sb) == -1)
+ return 0;
+
+ if (! writer(batchp, messagep)) {
+ if (S_ISREG(sb.st_mode)) {
+ ftruncate(messagep->mboxfd, sb.st_size);
+ return 0;
+ }
+ return 0;
+ }
+
+ return 1;
+}