aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/mda.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/mda.c')
-rw-r--r--smtpd/mda.c919
1 files changed, 919 insertions, 0 deletions
diff --git a/smtpd/mda.c b/smtpd/mda.c
new file mode 100644
index 00000000..5e8fec19
--- /dev/null
+++ b/smtpd/mda.c
@@ -0,0 +1,919 @@
+/* $OpenBSD: mda.c,v 1.141 2019/10/03 08:50:08 gilles Exp $ */
+
+/*
+ * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <grp.h> /* needed for setgroups */
+#include <imsg.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
+#include <vis.h>
+#else
+#include "bsd-vis.h"
+#endif
+
+#include "smtpd.h"
+#include "log.h"
+
+#define MDA_HIWAT 65536
+
+struct mda_envelope {
+ TAILQ_ENTRY(mda_envelope) entry;
+ uint64_t session_id;
+ uint64_t id;
+ time_t creation;
+ char *sender;
+ char *rcpt;
+ char *dest;
+ char *user;
+ char *dispatcher;
+ char *mda_subaddress;
+ char *mda_exec;
+};
+
+#define USER_WAITINFO 0x01
+#define USER_RUNNABLE 0x02
+#define USER_ONHOLD 0x04
+#define USER_HOLDQ 0x08
+
+struct mda_user {
+ uint64_t id;
+ TAILQ_ENTRY(mda_user) entry;
+ TAILQ_ENTRY(mda_user) entry_runnable;
+ char name[LOGIN_NAME_MAX];
+ char usertable[PATH_MAX];
+ size_t evpcount;
+ TAILQ_HEAD(, mda_envelope) envelopes;
+ int flags;
+ size_t running;
+ struct userinfo userinfo;
+};
+
+struct mda_session {
+ uint64_t id;
+ struct mda_user *user;
+ struct mda_envelope *evp;
+ struct io *io;
+ FILE *datafp;
+};
+
+static void mda_io(struct io *, int, void *);
+static int mda_check_loop(FILE *, struct mda_envelope *);
+static int mda_getlastline(int, char *, size_t);
+static void mda_done(struct mda_session *);
+static void mda_fail(struct mda_user *, int, const char *,
+ enum enhanced_status_code);
+static void mda_drain(void);
+static void mda_log(const struct mda_envelope *, const char *, const char *);
+static void mda_queue_ok(uint64_t);
+static void mda_queue_tempfail(uint64_t, const char *,
+ enum enhanced_status_code);
+static void mda_queue_permfail(uint64_t, const char *, enum enhanced_status_code);
+static void mda_queue_loop(uint64_t);
+static struct mda_user *mda_user(const struct envelope *);
+static void mda_user_free(struct mda_user *);
+static const char *mda_user_to_text(const struct mda_user *);
+static struct mda_envelope *mda_envelope(uint64_t, const struct envelope *);
+static void mda_envelope_free(struct mda_envelope *);
+static struct mda_session * mda_session(struct mda_user *);
+static const char *mda_sysexit_to_str(int);
+
+static struct tree sessions;
+static struct tree users;
+
+static TAILQ_HEAD(, mda_user) runnable;
+
+void
+mda_imsg(struct mproc *p, struct imsg *imsg)
+{
+ struct mda_session *s;
+ struct mda_user *u;
+ struct mda_envelope *e;
+ struct envelope evp;
+ struct deliver deliver;
+ struct msg m;
+ const void *data;
+ const char *error, *parent_error, *syserror;
+ uint64_t reqid;
+ size_t sz;
+ char out[256], buf[LINE_MAX];
+ int n;
+ enum lka_resp_status status;
+ enum mda_resp_status mda_status;
+ int mda_sysexit;
+
+ switch (imsg->hdr.type) {
+ case IMSG_MDA_LOOKUP_USERINFO:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_int(&m, (int *)&status);
+ if (status == LKA_OK)
+ m_get_data(&m, &data, &sz);
+ m_end(&m);
+
+ u = tree_xget(&users, reqid);
+
+ if (status == LKA_TEMPFAIL)
+ mda_fail(u, 0,
+ "Temporary failure in user lookup",
+ ESC_OTHER_ADDRESS_STATUS);
+ else if (status == LKA_PERMFAIL)
+ mda_fail(u, 1,
+ "Permanent failure in user lookup",
+ ESC_DESTINATION_MAILBOX_HAS_MOVED);
+ else {
+ if (sz != sizeof(u->userinfo))
+ fatalx("mda: userinfo size mismatch");
+ memmove(&u->userinfo, data, sz);
+ u->flags &= ~USER_WAITINFO;
+ u->flags |= USER_RUNNABLE;
+ TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
+ mda_drain();
+ }
+ return;
+
+ case IMSG_QUEUE_DELIVER:
+ m_msg(&m, imsg);
+ m_get_envelope(&m, &evp);
+ m_end(&m);
+
+ u = mda_user(&evp);
+
+ if (u->evpcount >= env->sc_mda_task_hiwat) {
+ if (!(u->flags & USER_ONHOLD)) {
+ log_debug("debug: mda: hiwat reached for "
+ "user \"%s\": holding envelopes",
+ mda_user_to_text(u));
+ u->flags |= USER_ONHOLD;
+ }
+ }
+
+ if (u->flags & USER_ONHOLD) {
+ u->flags |= USER_HOLDQ;
+ m_create(p_queue, IMSG_MDA_DELIVERY_HOLD,
+ 0, 0, -1);
+ m_add_evpid(p_queue, evp.id);
+ m_add_id(p_queue, u->id);
+ m_close(p_queue);
+ return;
+ }
+
+ e = mda_envelope(u->id, &evp);
+ TAILQ_INSERT_TAIL(&u->envelopes, e, entry);
+ u->evpcount += 1;
+ stat_increment("mda.pending", 1);
+
+ if (!(u->flags & USER_RUNNABLE) &&
+ !(u->flags & USER_WAITINFO)) {
+ u->flags |= USER_RUNNABLE;
+ TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
+ }
+
+ mda_drain();
+ return;
+
+ case IMSG_MDA_OPEN_MESSAGE:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_end(&m);
+
+ s = tree_xget(&sessions, reqid);
+ e = s->evp;
+
+ if (imsg->fd == -1) {
+ log_debug("debug: mda: cannot get message fd");
+ mda_queue_tempfail(e->id,
+ "Cannot get message fd",
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ mda_log(e, "TempFail", "Cannot get message fd");
+ mda_done(s);
+ return;
+ }
+
+ log_debug("debug: mda: got message fd %d "
+ "for session %016"PRIx64 " evpid %016"PRIx64,
+ imsg->fd, s->id, e->id);
+
+ if ((s->datafp = fdopen(imsg->fd, "r")) == NULL) {
+ log_warn("warn: mda: fdopen");
+ close(imsg->fd);
+ mda_queue_tempfail(e->id, "fdopen failed",
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ mda_log(e, "TempFail", "fdopen failed");
+ mda_done(s);
+ return;
+ }
+
+ /* check delivery loop */
+ if (mda_check_loop(s->datafp, e)) {
+ log_debug("debug: mda: loop detected");
+ mda_queue_loop(e->id);
+ mda_log(e, "PermFail", "Loop detected");
+ mda_done(s);
+ return;
+ }
+
+ /* start queueing delivery headers */
+ if (e->sender[0])
+ /*
+ * XXX: remove existing Return-Path,
+ * if any
+ */
+ n = io_printf(s->io,
+ "Return-Path: <%s>\n"
+ "Delivered-To: %s\n",
+ e->sender,
+ e->rcpt ? e->rcpt : e->dest);
+ else
+ n = io_printf(s->io,
+ "Delivered-To: %s\n",
+ e->rcpt ? e->rcpt : e->dest);
+ if (n == -1) {
+ log_warn("warn: mda: "
+ "fail to write delivery info");
+ mda_queue_tempfail(e->id, "Out of memory",
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ mda_log(e, "TempFail", "Out of memory");
+ mda_done(s);
+ return;
+ }
+
+ /* request parent to fork a helper process */
+ memset(&deliver, 0, sizeof deliver);
+ (void)text_to_mailaddr(&deliver.sender, s->evp->sender);
+ (void)text_to_mailaddr(&deliver.rcpt, s->evp->rcpt);
+ (void)text_to_mailaddr(&deliver.dest, s->evp->dest);
+ if (s->evp->mda_exec)
+ (void)strlcpy(deliver.mda_exec, s->evp->mda_exec, sizeof deliver.mda_exec);
+ if (s->evp->mda_subaddress)
+ (void)strlcpy(deliver.mda_subaddress, s->evp->mda_subaddress, sizeof deliver.mda_subaddress);
+ (void)strlcpy(deliver.dispatcher, s->evp->dispatcher, sizeof deliver.dispatcher);
+ deliver.userinfo = s->user->userinfo;
+
+ log_debug("debug: mda: querying mda fd "
+ "for session %016"PRIx64 " evpid %016"PRIx64,
+ s->id, s->evp->id);
+
+ m_create(p_parent, IMSG_MDA_FORK, 0, 0, -1);
+ m_add_id(p_parent, reqid);
+ m_add_data(p_parent, &deliver, sizeof(deliver));
+ m_close(p_parent);
+ return;
+
+ case IMSG_MDA_FORK:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_end(&m);
+
+ s = tree_xget(&sessions, reqid);
+ e = s->evp;
+ if (imsg->fd == -1) {
+ log_warn("warn: mda: fail to retrieve mda fd");
+ mda_queue_tempfail(e->id, "Cannot get mda fd",
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ mda_log(e, "TempFail", "Cannot get mda fd");
+ mda_done(s);
+ return;
+ }
+
+ log_debug("debug: mda: got mda fd %d "
+ "for session %016"PRIx64 " evpid %016"PRIx64,
+ imsg->fd, s->id, s->evp->id);
+
+ io_set_nonblocking(imsg->fd);
+ io_set_fd(s->io, imsg->fd);
+ io_set_write(s->io);
+ return;
+
+ case IMSG_MDA_DONE:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_int(&m, (int *)&mda_status);
+ m_get_int(&m, (int *)&mda_sysexit);
+ m_get_string(&m, &parent_error);
+ m_end(&m);
+
+ s = tree_xget(&sessions, reqid);
+ e = s->evp;
+ /*
+ * Grab last line of mda stdout/stderr if available.
+ */
+ out[0] = '\0';
+ if (imsg->fd != -1)
+ mda_getlastline(imsg->fd, out, sizeof(out));
+
+ /*
+ * Choose between parent's description of error and
+ * child's output, the latter having preference over
+ * the former.
+ */
+ error = NULL;
+ if (mda_status == MDA_OK) {
+ if (s->datafp || (s->io && io_queued(s->io))) {
+ error = "mda exited prematurely";
+ mda_status = MDA_TEMPFAIL;
+ }
+ } else
+ error = out[0] ? out : parent_error;
+
+ syserror = NULL;
+ if (mda_sysexit)
+ syserror = mda_sysexit_to_str(mda_sysexit);
+
+ /* update queue entry */
+ switch (mda_status) {
+ case MDA_TEMPFAIL:
+ mda_queue_tempfail(e->id, error,
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ (void)snprintf(buf, sizeof buf,
+ "Error (%s%s%s)",
+ syserror ? syserror : "",
+ syserror ? ": " : "",
+ error);
+ mda_log(e, "TempFail", buf);
+ break;
+ case MDA_PERMFAIL:
+ mda_queue_permfail(e->id, error,
+ ESC_OTHER_MAIL_SYSTEM_STATUS);
+ (void)snprintf(buf, sizeof buf,
+ "Error (%s%s%s)",
+ syserror ? syserror : "",
+ syserror ? ": " : "",
+ error);
+ mda_log(e, "PermFail", buf);
+ break;
+ case MDA_OK:
+ mda_queue_ok(e->id);
+ mda_log(e, "Ok", "Delivered");
+ break;
+ }
+ mda_done(s);
+ return;
+ }
+
+ errx(1, "mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type));
+}
+
+void
+mda_postfork()
+{
+}
+
+void
+mda_postprivdrop()
+{
+ tree_init(&sessions);
+ tree_init(&users);
+ TAILQ_INIT(&runnable);
+}
+
+static void
+mda_io(struct io *io, int evt, void *arg)
+{
+ struct mda_session *s = arg;
+ char *ln = NULL;
+ size_t sz = 0;
+ ssize_t len;
+
+ log_trace(TRACE_IO, "mda: %p: %s %s", s, io_strevent(evt),
+ io_strio(io));
+
+ switch (evt) {
+ case IO_LOWAT:
+
+ /* done */
+ done:
+ if (s->datafp == NULL) {
+ log_debug("debug: mda: all data sent for session"
+ " %016"PRIx64 " evpid %016"PRIx64,
+ s->id, s->evp->id);
+ io_free(io);
+ s->io = NULL;
+ return;
+ }
+
+ while (io_queued(s->io) < MDA_HIWAT) {
+ if ((len = getline(&ln, &sz, s->datafp)) == -1)
+ break;
+ if (io_write(s->io, ln, len) == -1) {
+ m_create(p_parent, IMSG_MDA_KILL,
+ 0, 0, -1);
+ m_add_id(p_parent, s->id);
+ m_add_string(p_parent, "Out of memory");
+ m_close(p_parent);
+ io_pause(io, IO_OUT);
+ free(ln);
+ return;
+ }
+ }
+
+ free(ln);
+ ln = NULL;
+ if (ferror(s->datafp)) {
+ log_debug("debug: mda: ferror on session %016"PRIx64,
+ s->id);
+ m_create(p_parent, IMSG_MDA_KILL, 0, 0, -1);
+ m_add_id(p_parent, s->id);
+ m_add_string(p_parent, "Error reading body");
+ m_close(p_parent);
+ io_pause(io, IO_OUT);
+ return;
+ }
+
+ if (feof(s->datafp)) {
+ log_debug("debug: mda: end-of-file for session"
+ " %016"PRIx64 " evpid %016"PRIx64,
+ s->id, s->evp->id);
+ fclose(s->datafp);
+ s->datafp = NULL;
+ if (io_queued(s->io) == 0)
+ goto done;
+ }
+ return;
+
+ case IO_TIMEOUT:
+ log_debug("debug: mda: timeout on session %016"PRIx64, s->id);
+ io_pause(io, IO_OUT);
+ return;
+
+ case IO_ERROR:
+ log_debug("debug: mda: io error on session %016"PRIx64": %s",
+ s->id, io_error(io));
+ io_pause(io, IO_OUT);
+ return;
+
+ case IO_DISCONNECTED:
+ log_debug("debug: mda: io disconnected on session %016"PRIx64,
+ s->id);
+ io_pause(io, IO_OUT);
+ return;
+
+ default:
+ log_debug("debug: mda: unexpected event on session %016"PRIx64,
+ s->id);
+ io_pause(io, IO_OUT);
+ return;
+ }
+}
+
+static int
+mda_check_loop(FILE *fp, struct mda_envelope *e)
+{
+ char *buf = NULL;
+ size_t sz = 0;
+ ssize_t len;
+ int ret = 0;
+
+ while ((len = getline(&buf, &sz, fp)) != -1) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+
+ if (strchr(buf, ':') == NULL && !isspace((unsigned char)*buf))
+ break;
+
+ if (strncasecmp("Delivered-To: ", buf, 14) == 0) {
+ if (strcasecmp(buf + 14, e->dest) == 0) {
+ ret = 1;
+ break;
+ }
+ }
+ }
+
+ free(buf);
+ fseek(fp, SEEK_SET, 0);
+ return (ret);
+}
+
+static int
+mda_getlastline(int fd, char *dst, size_t dstsz)
+{
+ FILE *fp;
+ char *ln = NULL;
+ size_t sz = 0;
+ ssize_t len;
+ int out = 0;
+
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ log_warn("warn: mda: lseek");
+ close(fd);
+ return (-1);
+ }
+ fp = fdopen(fd, "r");
+ if (fp == NULL) {
+ log_warn("warn: mda: fdopen");
+ close(fd);
+ return (-1);
+ }
+ while ((len = getline(&ln, &sz, fp)) != -1) {
+ if (ln[len - 1] == '\n')
+ ln[len - 1] = '\0';
+ out = 1;
+ }
+ fclose(fp);
+
+ if (out) {
+ (void)strlcpy(dst, "\"", dstsz);
+ (void)strnvis(dst + 1, ln, dstsz - 2, VIS_SAFE | VIS_CSTYLE | VIS_NL);
+ (void)strlcat(dst, "\"", dstsz);
+ }
+
+ free(ln);
+ return (0);
+}
+
+static void
+mda_fail(struct mda_user *user, int permfail, const char *error,
+ enum enhanced_status_code code)
+{
+ struct mda_envelope *e;
+
+ while ((e = TAILQ_FIRST(&user->envelopes))) {
+ TAILQ_REMOVE(&user->envelopes, e, entry);
+ if (permfail) {
+ mda_log(e, "PermFail", error);
+ mda_queue_permfail(e->id, error, code);
+ }
+ else {
+ mda_log(e, "TempFail", error);
+ mda_queue_tempfail(e->id, error, code);
+ }
+ mda_envelope_free(e);
+ }
+
+ mda_user_free(user);
+}
+
+static void
+mda_drain(void)
+{
+ struct mda_user *u;
+
+ while ((u = (TAILQ_FIRST(&runnable)))) {
+
+ TAILQ_REMOVE(&runnable, u, entry_runnable);
+
+ if (u->evpcount == 0 && u->running == 0) {
+ log_debug("debug: mda: all done for user \"%s\"",
+ mda_user_to_text(u));
+ mda_user_free(u);
+ continue;
+ }
+
+ if (u->evpcount == 0) {
+ log_debug("debug: mda: no more envelope for \"%s\"",
+ mda_user_to_text(u));
+ u->flags &= ~USER_RUNNABLE;
+ continue;
+ }
+
+ if (u->running >= env->sc_mda_max_user_session) {
+ log_debug("debug: mda: "
+ "maximum number of session reached for user \"%s\"",
+ mda_user_to_text(u));
+ u->flags &= ~USER_RUNNABLE;
+ continue;
+ }
+
+ if (tree_count(&sessions) >= env->sc_mda_max_session) {
+ log_debug("debug: mda: "
+ "maximum number of session reached");
+ TAILQ_INSERT_HEAD(&runnable, u, entry_runnable);
+ return;
+ }
+
+ mda_session(u);
+
+ if (u->evpcount == env->sc_mda_task_lowat) {
+ if (u->flags & USER_ONHOLD) {
+ log_debug("debug: mda: down to lowat for user "
+ "\"%s\": releasing",
+ mda_user_to_text(u));
+ u->flags &= ~USER_ONHOLD;
+ }
+ if (u->flags & USER_HOLDQ) {
+ m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE,
+ 0, 0, -1);
+ m_add_id(p_queue, u->id);
+ m_add_int(p_queue, env->sc_mda_task_release);
+ m_close(p_queue);
+ }
+ }
+
+ /* re-add the user at the tail of the queue */
+ TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
+ }
+}
+
+static void
+mda_done(struct mda_session *s)
+{
+ log_debug("debug: mda: session %016" PRIx64 " done", s->id);
+
+ tree_xpop(&sessions, s->id);
+
+ mda_envelope_free(s->evp);
+
+ s->user->running--;
+ if (!(s->user->flags & USER_RUNNABLE)) {
+ log_debug("debug: mda: user \"%s\" becomes runnable",
+ s->user->name);
+ TAILQ_INSERT_TAIL(&runnable, s->user, entry_runnable);
+ s->user->flags |= USER_RUNNABLE;
+ }
+
+ if (s->datafp)
+ fclose(s->datafp);
+ if (s->io)
+ io_free(s->io);
+
+ free(s);
+
+ stat_decrement("mda.running", 1);
+
+ mda_drain();
+}
+
+static void
+mda_log(const struct mda_envelope *evp, const char *prefix, const char *status)
+{
+ char rcpt[LINE_MAX];
+
+ rcpt[0] = '\0';
+ if (evp->rcpt)
+ (void)snprintf(rcpt, sizeof rcpt, "rcpt=<%s> ", evp->rcpt);
+
+ log_info("%016"PRIx64" mda delivery evpid=%016" PRIx64 " from=<%s> to=<%s> "
+ "%suser=%s delay=%s result=%s stat=%s",
+ evp->session_id,
+ evp->id,
+ evp->sender ? evp->sender : "",
+ evp->dest,
+ rcpt,
+ evp->user,
+ duration_to_text(time(NULL) - evp->creation),
+ prefix,
+ status);
+}
+
+static void
+mda_queue_ok(uint64_t evpid)
+{
+ m_create(p_queue, IMSG_MDA_DELIVERY_OK, 0, 0, -1);
+ m_add_evpid(p_queue, evpid);
+ m_close(p_queue);
+}
+
+static void
+mda_queue_tempfail(uint64_t evpid, const char *reason,
+ enum enhanced_status_code code)
+{
+ m_create(p_queue, IMSG_MDA_DELIVERY_TEMPFAIL, 0, 0, -1);
+ m_add_evpid(p_queue, evpid);
+ m_add_string(p_queue, reason);
+ m_add_int(p_queue, (int)code);
+ m_close(p_queue);
+}
+
+static void
+mda_queue_permfail(uint64_t evpid, const char *reason,
+ enum enhanced_status_code code)
+{
+ m_create(p_queue, IMSG_MDA_DELIVERY_PERMFAIL, 0, 0, -1);
+ m_add_evpid(p_queue, evpid);
+ m_add_string(p_queue, reason);
+ m_add_int(p_queue, (int)code);
+ m_close(p_queue);
+}
+
+static void
+mda_queue_loop(uint64_t evpid)
+{
+ m_create(p_queue, IMSG_MDA_DELIVERY_LOOP, 0, 0, -1);
+ m_add_evpid(p_queue, evpid);
+ m_close(p_queue);
+}
+
+static struct mda_user *
+mda_user(const struct envelope *evp)
+{
+ struct dispatcher *dsp;
+ struct mda_user *u;
+ void *i;
+
+ i = NULL;
+ dsp = dict_xget(env->sc_dispatchers, evp->dispatcher);
+ while (tree_iter(&users, &i, NULL, (void**)(&u))) {
+ if (!strcmp(evp->mda_user, u->name) &&
+ !strcmp(dsp->u.local.table_userbase, u->usertable))
+ return (u);
+ }
+
+ u = xcalloc(1, sizeof *u);
+ u->id = generate_uid();
+ TAILQ_INIT(&u->envelopes);
+ (void)strlcpy(u->name, evp->mda_user, sizeof(u->name));
+ (void)strlcpy(u->usertable, dsp->u.local.table_userbase,
+ sizeof(u->usertable));
+
+ tree_xset(&users, u->id, u);
+
+ m_create(p_lka, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1);
+ m_add_id(p_lka, u->id);
+ m_add_string(p_lka, dsp->u.local.table_userbase);
+ m_add_string(p_lka, evp->mda_user);
+ m_close(p_lka);
+ u->flags |= USER_WAITINFO;
+
+ stat_increment("mda.user", 1);
+
+ if (dsp->u.local.user)
+ log_debug("mda: new user %016" PRIx64
+ " for \"%s\" delivering as \"%s\"",
+ u->id, mda_user_to_text(u), dsp->u.local.user);
+ else
+ log_debug("mda: new user %016" PRIx64
+ " for \"%s\"", u->id, mda_user_to_text(u));
+
+ return (u);
+}
+
+static void
+mda_user_free(struct mda_user *u)
+{
+ tree_xpop(&users, u->id);
+
+ if (u->flags & USER_HOLDQ) {
+ m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, 0, 0, -1);
+ m_add_id(p_queue, u->id);
+ m_add_int(p_queue, 0);
+ m_close(p_queue);
+ }
+
+ free(u);
+ stat_decrement("mda.user", 1);
+}
+
+static const char *
+mda_user_to_text(const struct mda_user *u)
+{
+ static char buf[1024];
+
+ (void)snprintf(buf, sizeof(buf), "%s:%s", u->usertable, u->name);
+
+ return (buf);
+}
+
+static struct mda_envelope *
+mda_envelope(uint64_t session_id, const struct envelope *evp)
+{
+ struct mda_envelope *e;
+ char buf[LINE_MAX];
+
+ e = xcalloc(1, sizeof *e);
+ e->session_id = session_id;
+ e->id = evp->id;
+ e->creation = evp->creation;
+ buf[0] = '\0';
+ if (evp->sender.user[0] && evp->sender.domain[0])
+ (void)snprintf(buf, sizeof buf, "%s@%s",
+ evp->sender.user, evp->sender.domain);
+ e->sender = xstrdup(buf);
+ (void)snprintf(buf, sizeof buf, "%s@%s", evp->dest.user,
+ evp->dest.domain);
+ e->dest = xstrdup(buf);
+ (void)snprintf(buf, sizeof buf, "%s@%s", evp->rcpt.user,
+ evp->rcpt.domain);
+ e->rcpt = xstrdup(buf);
+ e->user = evp->mda_user[0] ?
+ xstrdup(evp->mda_user) : xstrdup(evp->dest.user);
+ e->dispatcher = xstrdup(evp->dispatcher);
+ if (evp->mda_exec[0])
+ e->mda_exec = xstrdup(evp->mda_exec);
+ if (evp->mda_subaddress[0])
+ e->mda_subaddress = xstrdup(evp->mda_subaddress);
+ stat_increment("mda.envelope", 1);
+ return (e);
+}
+
+static void
+mda_envelope_free(struct mda_envelope *e)
+{
+ free(e->sender);
+ free(e->dest);
+ free(e->rcpt);
+ free(e->user);
+ free(e->mda_exec);
+ free(e);
+
+ stat_decrement("mda.envelope", 1);
+}
+
+static struct mda_session *
+mda_session(struct mda_user * u)
+{
+ struct mda_session *s;
+
+ s = xcalloc(1, sizeof *s);
+ s->id = generate_uid();
+ s->user = u;
+ s->io = io_new();
+ io_set_callback(s->io, mda_io, s);
+
+ tree_xset(&sessions, s->id, s);
+
+ s->evp = TAILQ_FIRST(&u->envelopes);
+ TAILQ_REMOVE(&u->envelopes, s->evp, entry);
+ u->evpcount--;
+ u->running++;
+
+ stat_decrement("mda.pending", 1);
+ stat_increment("mda.running", 1);
+
+ log_debug("debug: mda: new session %016" PRIx64
+ " for user \"%s\" evpid %016" PRIx64, s->id,
+ mda_user_to_text(u), s->evp->id);
+
+ m_create(p_queue, IMSG_MDA_OPEN_MESSAGE, 0, 0, -1);
+ m_add_id(p_queue, s->id);
+ m_add_msgid(p_queue, evpid_to_msgid(s->evp->id));
+ m_close(p_queue);
+
+ return (s);
+}
+
+static const char *
+mda_sysexit_to_str(int sysexit)
+{
+ switch (sysexit) {
+ case EX_USAGE:
+ return "command line usage error";
+ case EX_DATAERR:
+ return "data format error";
+ case EX_NOINPUT:
+ return "cannot open input";
+ case EX_NOUSER:
+ return "user unknown";
+ case EX_NOHOST:
+ return "host name unknown";
+ case EX_UNAVAILABLE:
+ return "service unavailable";
+ case EX_SOFTWARE:
+ return "internal software error";
+ case EX_OSERR:
+ return "system resource problem";
+ case EX_OSFILE:
+ return "critical OS file missing";
+ case EX_CANTCREAT:
+ return "can't create user output file";
+ case EX_IOERR:
+ return "input/output error";
+ case EX_TEMPFAIL:
+ return "temporary failure";
+ case EX_PROTOCOL:
+ return "remote error in protocol";
+ case EX_NOPERM:
+ return "permission denied";
+ case EX_CONFIG:
+ return "local configuration error";
+ default:
+ break;
+ }
+ return NULL;
+}
+