src/usr.sbin/smtpd/mda.c

902 lines
21 KiB
C

/* $OpenBSD: mda.c,v 1.147 2024/01/20 09:01:03 claudio 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 <ctype.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include <vis.h>
#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, fd;
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;
fd = imsg_get_fd(imsg);
if (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,
fd, s->id, e->id);
if ((s->datafp = fdopen(fd, "r")) == NULL) {
log_warn("warn: mda: fdopen");
close(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;
fd = imsg_get_fd(imsg);
if (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,
fd, s->id, s->evp->id);
io_set_nonblocking(fd);
io_set_fd(s->io, 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';
fd = imsg_get_fd(imsg);
if (fd != -1)
mda_getlastline(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;
}
fatalx("mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type));
}
void
mda_postfork(void)
{
}
void
mda_postprivdrop(void)
{
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) == -1) {
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;
}