aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/smtpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/smtpd.c')
-rw-r--r--smtpd/smtpd.c2328
1 files changed, 2328 insertions, 0 deletions
diff --git a/smtpd/smtpd.c b/smtpd/smtpd.c
new file mode 100644
index 00000000..f18b1446
--- /dev/null
+++ b/smtpd/smtpd.c
@@ -0,0 +1,2328 @@
+/* $OpenBSD: smtpd.c,v 1.333 2020/05/06 16:03:30 millert 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>
+ *
+ * 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/file.h> /* Needed for flock */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+
+#ifdef BSD_AUTH
+#include <bsd_auth.h>
+#endif
+
+#ifdef USE_PAM
+#if defined(HAVE_SECURITY_PAM_APPL_H)
+#include <security/pam_appl.h>
+#elif defined (HAVE_PAM_PAM_APPL_H)
+#include <pam/pam_appl.h>
+#endif
+#endif
+
+#ifdef HAVE_CRYPT_H
+#include <crypt.h> /* needed for crypt() */
+#endif
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <grp.h> /* needed for setgroups */
+#include <fts.h>
+#include <grp.h>
+#include <imsg.h>
+#include <inttypes.h>
+#include <libgen.h>
+#ifdef HAVE_LOGIN_CAP_H
+#include <login_cap.h>
+#endif
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <poll.h>
+#include <pwd.h>
+#include <signal.h>
+#ifdef HAVE_SHADOW_H
+#include <shadow.h> /* needed for getspnam() */
+#endif
+#include <stdio.h>
+#include <syslog.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+
+#include "smtpd.h"
+#include "log.h"
+#include "ssl.h"
+
+extern char *__progname;
+
+#define SMTPD_MAXARG 32
+
+static void parent_imsg(struct mproc *, struct imsg *);
+static void usage(void);
+static int smtpd(void);
+static void parent_shutdown(void);
+static void parent_send_config(int, short, void *);
+static void parent_send_config_lka(void);
+static void parent_send_config_pony(void);
+static void parent_send_config_ca(void);
+static void parent_sig_handler(int, short, void *);
+static void forkmda(struct mproc *, uint64_t, struct deliver *);
+static int parent_forward_open(char *, char *, uid_t, gid_t);
+static struct child *child_add(pid_t, int, const char *);
+static struct mproc *start_child(int, char **, char *);
+static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int);
+static void setup_peers(struct mproc *, struct mproc *);
+static void setup_done(struct mproc *);
+static void setup_proc(void);
+static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int);
+static int imsg_wait(struct imsgbuf *, struct imsg *, int);
+
+static void offline_scan(int, short, void *);
+static int offline_add(char *, uid_t, gid_t);
+static void offline_done(void);
+static int offline_enqueue(char *, uid_t, gid_t);
+
+static void purge_task(void);
+static int parent_auth_user(const char *, const char *);
+static void load_pki_tree(void);
+static void load_pki_keys(void);
+
+static void fork_filter_processes(void);
+static void fork_filter_process(const char *, const char *, const char *, const char *, const char *, uint32_t);
+
+enum child_type {
+ CHILD_DAEMON,
+ CHILD_MDA,
+ CHILD_PROCESSOR,
+ CHILD_ENQUEUE_OFFLINE,
+};
+
+struct child {
+ pid_t pid;
+ enum child_type type;
+ const char *title;
+ int mda_out;
+ uint64_t mda_id;
+ char *path;
+ char *cause;
+};
+
+struct offline {
+ TAILQ_ENTRY(offline) entry;
+ uid_t uid;
+ gid_t gid;
+ char *path;
+};
+
+#define OFFLINE_READMAX 20
+#define OFFLINE_QUEUEMAX 5
+static size_t offline_running = 0;
+TAILQ_HEAD(, offline) offline_q;
+
+static struct event config_ev;
+static struct event offline_ev;
+static struct timeval offline_timeout;
+
+static pid_t purge_pid = -1;
+
+extern char **environ;
+void (*imsg_callback)(struct mproc *, struct imsg *);
+
+enum smtp_proc_type smtpd_process;
+
+struct smtpd *env = NULL;
+
+struct mproc *p_control = NULL;
+struct mproc *p_lka = NULL;
+struct mproc *p_parent = NULL;
+struct mproc *p_queue = NULL;
+struct mproc *p_scheduler = NULL;
+struct mproc *p_pony = NULL;
+struct mproc *p_ca = NULL;
+
+const char *backend_queue = "fs";
+const char *backend_scheduler = "ramqueue";
+const char *backend_stat = "ram";
+
+int profiling = 0;
+int debug = 0;
+int foreground = 0;
+int control_socket = -1;
+
+struct tree children;
+
+/* Saved arguments to main(). */
+char **saved_argv;
+int saved_argc;
+
+static void
+parent_imsg(struct mproc *p, struct imsg *imsg)
+{
+ struct forward_req *fwreq;
+ struct filter_proc *processor;
+ struct deliver deliver;
+ struct child *c;
+ struct msg m;
+ const void *data;
+ const char *username, *password, *cause, *procname;
+ uint64_t reqid;
+ size_t sz;
+ void *i;
+ int fd, n, v, ret;
+
+ if (imsg == NULL)
+ fatalx("process %s socket closed", p->name);
+
+ switch (imsg->hdr.type) {
+ case IMSG_LKA_OPEN_FORWARD:
+ CHECK_IMSG_DATA_SIZE(imsg, sizeof *fwreq);
+ fwreq = imsg->data;
+ fd = parent_forward_open(fwreq->user, fwreq->directory,
+ fwreq->uid, fwreq->gid);
+ fwreq->status = 0;
+ if (fd == -1 && errno != ENOENT) {
+ if (errno == EAGAIN)
+ fwreq->status = -1;
+ }
+ else
+ fwreq->status = 1;
+ m_compose(p, IMSG_LKA_OPEN_FORWARD, 0, 0, fd,
+ fwreq, sizeof *fwreq);
+ return;
+
+ case IMSG_LKA_AUTHENTICATE:
+ /*
+ * If we reached here, it means we want root to lookup
+ * system user.
+ */
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_string(&m, &username);
+ m_get_string(&m, &password);
+ m_end(&m);
+
+ ret = parent_auth_user(username, password);
+
+ m_create(p, IMSG_LKA_AUTHENTICATE, 0, 0, -1);
+ m_add_id(p, reqid);
+ m_add_int(p, ret);
+ m_close(p);
+ return;
+
+ case IMSG_MDA_FORK:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_data(&m, &data, &sz);
+ m_end(&m);
+ if (sz != sizeof(deliver))
+ fatalx("expected deliver");
+ memmove(&deliver, data, sz);
+ forkmda(p, reqid, &deliver);
+ return;
+
+ case IMSG_MDA_KILL:
+ m_msg(&m, imsg);
+ m_get_id(&m, &reqid);
+ m_get_string(&m, &cause);
+ m_end(&m);
+
+ i = NULL;
+ while ((n = tree_iter(&children, &i, NULL, (void**)&c)))
+ if (c->type == CHILD_MDA &&
+ c->mda_id == reqid &&
+ c->cause == NULL)
+ break;
+ if (!n) {
+ log_debug("debug: smtpd: "
+ "kill request: proc not found");
+ return;
+ }
+
+ c->cause = xstrdup(cause);
+ log_debug("debug: smtpd: kill requested for %u: %s",
+ c->pid, c->cause);
+ kill(c->pid, SIGTERM);
+ return;
+
+ case IMSG_CTL_VERBOSE:
+ m_msg(&m, imsg);
+ m_get_int(&m, &v);
+ m_end(&m);
+ log_trace_verbose(v);
+ return;
+
+ case IMSG_CTL_PROFILE:
+ m_msg(&m, imsg);
+ m_get_int(&m, &v);
+ m_end(&m);
+ profiling = v;
+ return;
+
+ case IMSG_LKA_PROCESSOR_ERRFD:
+ m_msg(&m, imsg);
+ m_get_string(&m, &procname);
+ m_end(&m);
+
+ processor = dict_xget(env->sc_filter_processes_dict, procname);
+ m_create(p_lka, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, processor->errfd);
+ m_add_string(p_lka, procname);
+ m_close(p_lka);
+ return;
+ }
+
+ errx(1, "parent_imsg: unexpected %s imsg from %s",
+ imsg_to_str(imsg->hdr.type), proc_title(p->proc));
+}
+
+static void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dFhnv] [-D macro=value] "
+ "[-f file] [-P system] [-T trace]\n", __progname);
+ exit(1);
+}
+
+static void
+parent_shutdown(void)
+{
+ pid_t pid;
+
+ mproc_clear(p_ca);
+ mproc_clear(p_pony);
+ mproc_clear(p_control);
+ mproc_clear(p_lka);
+ mproc_clear(p_scheduler);
+ mproc_clear(p_queue);
+
+ do {
+ pid = waitpid(WAIT_MYPGRP, NULL, 0);
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ unlink(SMTPD_SOCKET);
+
+ log_info("Exiting");
+ exit(0);
+}
+
+static void
+parent_send_config(int fd, short event, void *p)
+{
+ parent_send_config_lka();
+ parent_send_config_pony();
+ parent_send_config_ca();
+ purge_config(PURGE_PKI);
+}
+
+static void
+parent_send_config_pony(void)
+{
+ log_debug("debug: parent_send_config: configuring pony process");
+ m_compose(p_pony, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ m_compose(p_pony, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+}
+
+void
+parent_send_config_lka()
+{
+ log_debug("debug: parent_send_config_ruleset: reloading");
+ m_compose(p_lka, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ m_compose(p_lka, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+}
+
+static void
+parent_send_config_ca(void)
+{
+ log_debug("debug: parent_send_config: configuring ca process");
+ m_compose(p_ca, IMSG_CONF_START, 0, 0, -1, NULL, 0);
+ m_compose(p_ca, IMSG_CONF_END, 0, 0, -1, NULL, 0);
+}
+
+static void
+parent_sig_handler(int sig, short event, void *p)
+{
+ struct child *child;
+ int status, fail;
+ pid_t pid;
+ char *cause;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ log_debug("debug: got signal %d", sig);
+ parent_shutdown();
+ /* NOT REACHED */
+
+ case SIGCHLD:
+ do {
+ int len;
+ enum mda_resp_status mda_status;
+ int mda_sysexit;
+
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ continue;
+
+ fail = 0;
+ if (WIFSIGNALED(status)) {
+ fail = 1;
+ len = asprintf(&cause, "terminated; signal %d",
+ WTERMSIG(status));
+ mda_status = MDA_TEMPFAIL;
+ mda_sysexit = 0;
+ } else if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ fail = 1;
+ len = asprintf(&cause,
+ "exited abnormally");
+ mda_sysexit = WEXITSTATUS(status);
+ if (mda_sysexit == EX_OSERR ||
+ mda_sysexit == EX_TEMPFAIL)
+ mda_status = MDA_TEMPFAIL;
+ else
+ mda_status = MDA_PERMFAIL;
+ } else {
+ len = asprintf(&cause, "exited okay");
+ mda_status = MDA_OK;
+ mda_sysexit = 0;
+ }
+ } else
+ /* WIFSTOPPED or WIFCONTINUED */
+ continue;
+
+ if (len == -1)
+ fatal("asprintf");
+
+ if (pid == purge_pid)
+ purge_pid = -1;
+
+ child = tree_pop(&children, pid);
+ if (child == NULL)
+ goto skip;
+
+ switch (child->type) {
+ case CHILD_PROCESSOR:
+ if (fail) {
+ log_warnx("warn: lost processor: %s %s",
+ child->title, cause);
+ parent_shutdown();
+ }
+ break;
+
+ case CHILD_DAEMON:
+ if (fail)
+ log_warnx("warn: lost child: %s %s",
+ child->title, cause);
+ break;
+
+ case CHILD_MDA:
+ if (WIFSIGNALED(status) &&
+ WTERMSIG(status) == SIGALRM) {
+ char *tmp;
+ if (asprintf(&tmp,
+ "terminated; timeout") != -1) {
+ free(cause);
+ cause = tmp;
+ }
+ }
+ else if (child->cause &&
+ WIFSIGNALED(status) &&
+ WTERMSIG(status) == SIGTERM) {
+ free(cause);
+ cause = child->cause;
+ child->cause = NULL;
+ }
+ free(child->cause);
+ log_debug("debug: smtpd: mda process done "
+ "for session %016"PRIx64 ": %s",
+ child->mda_id, cause);
+
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0,
+ child->mda_out);
+ m_add_id(p_pony, child->mda_id);
+ m_add_int(p_pony, mda_status);
+ m_add_int(p_pony, mda_sysexit);
+ m_add_string(p_pony, cause);
+ m_close(p_pony);
+
+ break;
+
+ case CHILD_ENQUEUE_OFFLINE:
+ if (fail)
+ log_warnx("warn: smtpd: "
+ "couldn't enqueue offline "
+ "message %s; smtpctl %s",
+ child->path, cause);
+ else
+ unlink(child->path);
+ free(child->path);
+ offline_done();
+ break;
+
+ default:
+ fatalx("smtpd: unexpected child type");
+ }
+ free(child);
+ skip:
+ free(cause);
+ } while (pid > 0 || (pid == -1 && errno == EINTR));
+
+ break;
+ default:
+ fatalx("smtpd: unexpected signal");
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, i;
+ int opts, flags;
+ const char *conffile = CONF_FILE;
+ int save_argc = argc;
+ char **save_argv = argv;
+ char *rexec = NULL;
+ struct smtpd *conf;
+
+#ifndef HAVE___PROGNAME
+ __progname = ssh_get_progname(argv[0]);
+#endif
+
+#ifndef HAVE_SETPROCTITLE
+ /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */
+ saved_argc = argc;
+ saved_argv = xcalloc(argc + 1, sizeof(*saved_argv));
+ for (i = 0; i < argc; i++)
+ saved_argv[i] = xstrdup(argv[i]);
+ saved_argv[i] = NULL;
+
+ /* Prepare for later setproctitle emulation */
+ compat_init_setproctitle(argc, argv);
+ argv = saved_argv;
+
+ /* this is to work around GNU getopt + portable setproctitle() fuckery */
+ save_argc = saved_argc;
+ save_argv = saved_argv;
+#endif
+
+ if ((conf = config_default()) == NULL)
+ err(1, NULL);
+
+ env = conf;
+
+ flags = 0;
+ opts = 0;
+ debug = 0;
+ tracing = 0;
+
+ log_init(1, LOG_MAIL);
+
+ TAILQ_INIT(&offline_q);
+
+ while ((c = getopt(argc, argv, "B:dD:hnP:f:FT:vx:")) != -1) {
+ switch (c) {
+ case 'B':
+ if (strstr(optarg, "queue=") == optarg)
+ backend_queue = strchr(optarg, '=') + 1;
+ else if (strstr(optarg, "scheduler=") == optarg)
+ backend_scheduler = strchr(optarg, '=') + 1;
+ else if (strstr(optarg, "stat=") == optarg)
+ backend_stat = strchr(optarg, '=') + 1;
+ else
+ log_warnx("warn: "
+ "invalid backend specifier %s",
+ optarg);
+ break;
+ case 'd':
+ foreground = 1;
+ foreground_log = 1;
+ break;
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("warn: "
+ "could not parse macro definition %s",
+ optarg);
+ break;
+ case 'h':
+ log_info("version: " SMTPD_NAME " " SMTPD_VERSION);
+ usage();
+ break;
+ case 'n':
+ debug = 2;
+ opts |= SMTPD_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'F':
+ foreground = 1;
+ break;
+
+ case 'T':
+ if (!strcmp(optarg, "imsg"))
+ tracing |= TRACE_IMSG;
+ else if (!strcmp(optarg, "io"))
+ tracing |= TRACE_IO;
+ else if (!strcmp(optarg, "smtp"))
+ tracing |= TRACE_SMTP;
+ else if (!strcmp(optarg, "filters"))
+ tracing |= TRACE_FILTERS;
+ else if (!strcmp(optarg, "mta") ||
+ !strcmp(optarg, "transfer"))
+ tracing |= TRACE_MTA;
+ else if (!strcmp(optarg, "bounce") ||
+ !strcmp(optarg, "bounces"))
+ tracing |= TRACE_BOUNCE;
+ else if (!strcmp(optarg, "scheduler"))
+ tracing |= TRACE_SCHEDULER;
+ else if (!strcmp(optarg, "lookup"))
+ tracing |= TRACE_LOOKUP;
+ else if (!strcmp(optarg, "stat") ||
+ !strcmp(optarg, "stats"))
+ tracing |= TRACE_STAT;
+ else if (!strcmp(optarg, "rules"))
+ tracing |= TRACE_RULES;
+ else if (!strcmp(optarg, "mproc"))
+ tracing |= TRACE_MPROC;
+ else if (!strcmp(optarg, "expand"))
+ tracing |= TRACE_EXPAND;
+ else if (!strcmp(optarg, "table") ||
+ !strcmp(optarg, "tables"))
+ tracing |= TRACE_TABLES;
+ else if (!strcmp(optarg, "queue"))
+ tracing |= TRACE_QUEUE;
+ else if (!strcmp(optarg, "all"))
+ tracing |= ~TRACE_DEBUG;
+ else if (!strcmp(optarg, "profstat"))
+ profiling |= PROFILE_TOSTAT;
+ else if (!strcmp(optarg, "profile-imsg"))
+ profiling |= PROFILE_IMSG;
+ else if (!strcmp(optarg, "profile-queue"))
+ profiling |= PROFILE_QUEUE;
+ else
+ log_warnx("warn: unknown trace flag \"%s\"",
+ optarg);
+ break;
+ case 'P':
+ if (!strcmp(optarg, "smtp"))
+ flags |= SMTPD_SMTP_PAUSED;
+ else if (!strcmp(optarg, "mta"))
+ flags |= SMTPD_MTA_PAUSED;
+ else if (!strcmp(optarg, "mda"))
+ flags |= SMTPD_MDA_PAUSED;
+ break;
+ case 'v':
+ tracing |= TRACE_DEBUG;
+ break;
+ case 'x':
+ rexec = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc || *argv)
+ usage();
+
+ env->sc_opts |= opts;
+
+ ssl_init();
+
+ if (parse_config(conf, conffile, opts))
+ exit(1);
+
+ if (RAND_status() != 1)
+ errx(1, "PRNG is not seeded");
+
+ if (strlcpy(env->sc_conffile, conffile, PATH_MAX)
+ >= PATH_MAX)
+ errx(1, "config file exceeds PATH_MAX");
+
+ if (env->sc_opts & SMTPD_OPT_NOACTION) {
+ if (env->sc_queue_key &&
+ crypto_setup(env->sc_queue_key,
+ strlen(env->sc_queue_key)) == 0) {
+ fatalx("crypto_setup:"
+ "invalid key for queue encryption");
+ }
+ load_pki_tree();
+ load_pki_keys();
+ fprintf(stderr, "configuration OK\n");
+ exit(0);
+ }
+
+ env->sc_flags |= flags;
+
+ /* check for root privileges */
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ log_init(foreground_log, LOG_MAIL);
+ log_trace_verbose(tracing);
+ load_pki_tree();
+ load_pki_keys();
+
+ log_debug("debug: using \"%s\" queue backend", backend_queue);
+ log_debug("debug: using \"%s\" scheduler backend", backend_scheduler);
+ log_debug("debug: using \"%s\" stat backend", backend_stat);
+
+ if (env->sc_hostname[0] == '\0')
+ errx(1, "machine does not have a hostname set");
+ env->sc_uptime = time(NULL);
+
+ if (rexec == NULL) {
+ smtpd_process = PROC_PARENT;
+
+ if (env->sc_queue_flags & QUEUE_ENCRYPTION) {
+ if (env->sc_queue_key == NULL) {
+ char *password;
+
+ password = getpass("queue key: ");
+ if (password == NULL)
+ err(1, "getpass");
+
+ env->sc_queue_key = strdup(password);
+ explicit_bzero(password, strlen(password));
+ if (env->sc_queue_key == NULL)
+ err(1, "strdup");
+ }
+ else {
+ char *buf = NULL;
+ size_t sz = 0;
+ ssize_t len;
+
+ if (strcasecmp(env->sc_queue_key, "stdin") == 0) {
+ if ((len = getline(&buf, &sz, stdin)) == -1)
+ err(1, "getline");
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ env->sc_queue_key = buf;
+ }
+ }
+ }
+
+ log_info("info: %s %s starting", SMTPD_NAME, SMTPD_VERSION);
+
+ if (!foreground)
+ if (daemon(0, 0) == -1)
+ err(1, "failed to daemonize");
+
+ /* setup all processes */
+
+ p_ca = start_child(save_argc, save_argv, "ca");
+ p_ca->proc = PROC_CA;
+
+ p_control = start_child(save_argc, save_argv, "control");
+ p_control->proc = PROC_CONTROL;
+
+ p_lka = start_child(save_argc, save_argv, "lka");
+ p_lka->proc = PROC_LKA;
+
+ p_pony = start_child(save_argc, save_argv, "pony");
+ p_pony->proc = PROC_PONY;
+
+ p_queue = start_child(save_argc, save_argv, "queue");
+ p_queue->proc = PROC_QUEUE;
+
+ p_scheduler = start_child(save_argc, save_argv, "scheduler");
+ p_scheduler->proc = PROC_SCHEDULER;
+
+ setup_peers(p_control, p_ca);
+ setup_peers(p_control, p_lka);
+ setup_peers(p_control, p_pony);
+ setup_peers(p_control, p_queue);
+ setup_peers(p_control, p_scheduler);
+ setup_peers(p_pony, p_ca);
+ setup_peers(p_pony, p_lka);
+ setup_peers(p_pony, p_queue);
+ setup_peers(p_queue, p_lka);
+ setup_peers(p_queue, p_scheduler);
+
+ if (env->sc_queue_key) {
+ if (imsg_compose(&p_queue->imsgbuf, IMSG_SETUP_KEY, 0,
+ 0, -1, env->sc_queue_key, strlen(env->sc_queue_key)
+ + 1) == -1)
+ fatal("imsg_compose");
+ if (imsg_flush(&p_queue->imsgbuf) == -1)
+ fatal("imsg_flush");
+ }
+
+ setup_done(p_ca);
+ setup_done(p_control);
+ setup_done(p_lka);
+ setup_done(p_pony);
+ setup_done(p_queue);
+ setup_done(p_scheduler);
+
+ log_debug("smtpd: setup done");
+
+ return smtpd();
+ }
+
+ if (!strcmp(rexec, "ca")) {
+ smtpd_process = PROC_CA;
+ setup_proc();
+
+ return ca();
+ }
+
+ else if (!strcmp(rexec, "control")) {
+ smtpd_process = PROC_CONTROL;
+ setup_proc();
+
+ /* the control socket ensures that only one smtpd instance is running */
+ control_socket = control_create_socket();
+
+ env->sc_stat = stat_backend_lookup(backend_stat);
+ if (env->sc_stat == NULL)
+ errx(1, "could not find stat backend \"%s\"", backend_stat);
+
+ return control();
+ }
+
+ else if (!strcmp(rexec, "lka")) {
+ smtpd_process = PROC_LKA;
+ setup_proc();
+
+ return lka();
+ }
+
+ else if (!strcmp(rexec, "pony")) {
+ smtpd_process = PROC_PONY;
+ setup_proc();
+
+ return pony();
+ }
+
+ else if (!strcmp(rexec, "queue")) {
+ smtpd_process = PROC_QUEUE;
+ setup_proc();
+
+ if (env->sc_queue_flags & QUEUE_COMPRESSION)
+ env->sc_comp = compress_backend_lookup("gzip");
+
+ if (!queue_init(backend_queue, 1))
+ errx(1, "could not initialize queue backend");
+
+ return queue();
+ }
+
+ else if (!strcmp(rexec, "scheduler")) {
+ smtpd_process = PROC_SCHEDULER;
+ setup_proc();
+
+ for (i = 0; i < MAX_BOUNCE_WARN; i++) {
+ if (env->sc_bounce_warn[i] == 0)
+ break;
+ log_debug("debug: bounce warning after %s",
+ duration_to_text(env->sc_bounce_warn[i]));
+ }
+
+ return scheduler();
+ }
+
+ fatalx("bad rexec: %s", rexec);
+
+ return (1);
+}
+
+static struct mproc *
+start_child(int save_argc, char **save_argv, char *rexec)
+{
+ struct mproc *p;
+ char *argv[SMTPD_MAXARG];
+ int sp[2], argc = 0;
+ pid_t pid;
+
+ if (save_argc >= SMTPD_MAXARG - 2)
+ fatalx("too many arguments");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1)
+ fatal("socketpair");
+
+ io_set_nonblocking(sp[0]);
+ io_set_nonblocking(sp[1]);
+
+ switch (pid = fork()) {
+ case -1:
+ fatal("%s: fork", save_argv[0]);
+ case 0:
+ break;
+ default:
+ close(sp[0]);
+ p = calloc(1, sizeof(*p));
+ if (p == NULL)
+ fatal("calloc");
+ if((p->name = strdup(rexec)) == NULL)
+ fatal("strdup");
+ mproc_init(p, sp[1]);
+ p->pid = pid;
+ p->handler = parent_imsg;
+ return p;
+ }
+
+ if (sp[0] != 3) {
+ if (dup2(sp[0], 3) == -1)
+ fatal("%s: dup2", rexec);
+ } else if (fcntl(sp[0], F_SETFD, 0) == -1)
+ fatal("%s: fcntl", rexec);
+
+ xclosefrom(4);
+
+ for (argc = 0; argc < save_argc; argc++)
+ argv[argc] = save_argv[argc];
+ argv[argc++] = "-x";
+ argv[argc++] = rexec;
+ argv[argc++] = NULL;
+
+ execvp(argv[0], argv);
+ fatal("%s: execvp", rexec);
+}
+
+static void
+setup_peers(struct mproc *a, struct mproc *b)
+{
+ int sp[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1)
+ fatal("socketpair");
+
+ io_set_nonblocking(sp[0]);
+ io_set_nonblocking(sp[1]);
+
+ if (imsg_compose(&a->imsgbuf, IMSG_SETUP_PEER, b->proc, b->pid, sp[0],
+ NULL, 0) == -1)
+ fatal("imsg_compose");
+ if (imsg_flush(&a->imsgbuf) == -1)
+ fatal("imsg_flush");
+
+ if (imsg_compose(&b->imsgbuf, IMSG_SETUP_PEER, a->proc, a->pid, sp[1],
+ NULL, 0) == -1)
+ fatal("imsg_compose");
+ if (imsg_flush(&b->imsgbuf) == -1)
+ fatal("imsg_flush");
+}
+
+static void
+setup_done(struct mproc *p)
+{
+ struct imsg imsg;
+
+ if (imsg_compose(&p->imsgbuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1)
+ fatal("imsg_compose");
+ if (imsg_flush(&p->imsgbuf) == -1)
+ fatal("imsg_flush");
+
+ if (imsg_wait(&p->imsgbuf, &imsg, 10000) == -1)
+ fatal("imsg_wait");
+
+ if (imsg.hdr.type != IMSG_SETUP_DONE)
+ fatalx("expect IMSG_SETUP_DONE");
+
+ log_debug("setup_done: %s[%d] done", p->name, p->pid);
+
+ imsg_free(&imsg);
+}
+
+static void
+setup_proc(void)
+{
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ int setup = 1;
+
+ log_procinit(proc_title(smtpd_process));
+
+ p_parent = calloc(1, sizeof(*p_parent));
+ if (p_parent == NULL)
+ fatal("calloc");
+ if((p_parent->name = strdup("parent")) == NULL)
+ fatal("strdup");
+ p_parent->proc = PROC_PARENT;
+ p_parent->handler = imsg_dispatch;
+ mproc_init(p_parent, 3);
+
+ ibuf = &p_parent->imsgbuf;
+
+ while (setup) {
+ if (imsg_wait(ibuf, &imsg, 10000) == -1)
+ fatal("imsg_wait");
+
+ switch (imsg.hdr.type) {
+ case IMSG_SETUP_KEY:
+ env->sc_queue_key = strdup(imsg.data);
+ break;
+ case IMSG_SETUP_PEER:
+ setup_peer(imsg.hdr.peerid, imsg.hdr.pid, imsg.fd);
+ break;
+ case IMSG_SETUP_DONE:
+ setup = 0;
+ break;
+ default:
+ fatal("bad imsg %d", imsg.hdr.type);
+ }
+ imsg_free(&imsg);
+ }
+
+ if (imsg_compose(ibuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1)
+ fatal("imsg_compose");
+
+ if (imsg_flush(ibuf) == -1)
+ fatal("imsg_flush");
+
+ log_debug("setup_proc: %s done", proc_title(smtpd_process));
+}
+
+static struct mproc *
+setup_peer(enum smtp_proc_type proc, pid_t pid, int sock)
+{
+ struct mproc *p, **pp;
+
+ log_debug("setup_peer: %s -> %s[%u] fd=%d", proc_title(smtpd_process),
+ proc_title(proc), pid, sock);
+
+ if (sock == -1)
+ fatalx("peer socket not received");
+
+ switch (proc) {
+ case PROC_LKA:
+ pp = &p_lka;
+ break;
+ case PROC_QUEUE:
+ pp = &p_queue;
+ break;
+ case PROC_CONTROL:
+ pp = &p_control;
+ break;
+ case PROC_SCHEDULER:
+ pp = &p_scheduler;
+ break;
+ case PROC_PONY:
+ pp = &p_pony;
+ break;
+ case PROC_CA:
+ pp = &p_ca;
+ break;
+ default:
+ fatalx("unknown peer");
+ }
+
+ if (*pp)
+ fatalx("peer already set");
+
+ p = calloc(1, sizeof(*p));
+ if (p == NULL)
+ fatal("calloc");
+ if((p->name = strdup(proc_title(proc))) == NULL)
+ fatal("strdup");
+ mproc_init(p, sock);
+ p->pid = pid;
+ p->proc = proc;
+ p->handler = imsg_dispatch;
+
+ *pp = p;
+
+ return p;
+}
+
+static int
+imsg_wait(struct imsgbuf *ibuf, struct imsg *imsg, int timeout)
+{
+ struct pollfd pfd[1];
+ ssize_t n;
+
+ pfd[0].fd = ibuf->fd;
+ pfd[0].events = POLLIN;
+
+ while (1) {
+ if ((n = imsg_get(ibuf, imsg)) == -1)
+ return -1;
+ if (n)
+ return 1;
+
+ n = poll(pfd, 1, timeout);
+ if (n == -1)
+ return -1;
+ if (n == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+
+ if (((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) || n == 0)
+ return -1;
+ }
+}
+
+int
+smtpd(void) {
+ struct event ev_sigint;
+ struct event ev_sigterm;
+ struct event ev_sigchld;
+ struct event ev_sighup;
+ struct timeval tv;
+
+ imsg_callback = parent_imsg;
+
+ tree_init(&children);
+
+ child_add(p_queue->pid, CHILD_DAEMON, proc_title(PROC_QUEUE));
+ child_add(p_control->pid, CHILD_DAEMON, proc_title(PROC_CONTROL));
+ child_add(p_lka->pid, CHILD_DAEMON, proc_title(PROC_LKA));
+ child_add(p_scheduler->pid, CHILD_DAEMON, proc_title(PROC_SCHEDULER));
+ child_add(p_pony->pid, CHILD_DAEMON, proc_title(PROC_PONY));
+ child_add(p_ca->pid, CHILD_DAEMON, proc_title(PROC_CA));
+
+ event_init();
+
+ signal_set(&ev_sigint, SIGINT, parent_sig_handler, NULL);
+ signal_set(&ev_sigterm, SIGTERM, parent_sig_handler, NULL);
+ signal_set(&ev_sigchld, SIGCHLD, parent_sig_handler, NULL);
+ signal_set(&ev_sighup, SIGHUP, parent_sig_handler, NULL);
+ 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_peer(PROC_CONTROL);
+ config_peer(PROC_LKA);
+ config_peer(PROC_QUEUE);
+ config_peer(PROC_CA);
+ config_peer(PROC_PONY);
+
+ evtimer_set(&config_ev, parent_send_config, NULL);
+ memset(&tv, 0, sizeof(tv));
+ evtimer_add(&config_ev, &tv);
+
+ /* defer offline scanning for a second */
+ evtimer_set(&offline_ev, offline_scan, NULL);
+ offline_timeout.tv_sec = 1;
+ offline_timeout.tv_usec = 0;
+ evtimer_add(&offline_ev, &offline_timeout);
+
+ if (pidfile(NULL) < 0)
+ err(1, "pidfile");
+
+ fork_filter_processes();
+
+ purge_task();
+
+#if HAVE_PLEDGE
+ if (pledge("stdio rpath wpath cpath fattr tmppath "
+ "getpw sendfd proc exec id inet chown unix", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ event_dispatch();
+ fatalx("exited event loop");
+
+ return (0);
+}
+
+static void
+load_pki_tree(void)
+{
+ struct pki *pki;
+ struct ca *sca;
+ const char *k;
+ void *iter_dict;
+
+ log_debug("debug: init ssl-tree");
+ iter_dict = NULL;
+ while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) {
+ log_debug("info: loading pki information for %s", k);
+ if (pki->pki_cert_file == NULL)
+ fatalx("load_pki_tree: missing certificate file");
+ if (pki->pki_key_file == NULL)
+ fatalx("load_pki_tree: missing key file");
+
+ if (!ssl_load_certificate(pki, pki->pki_cert_file))
+ fatalx("load_pki_tree: failed to load certificate file");
+ }
+
+ log_debug("debug: init ca-tree");
+ iter_dict = NULL;
+ while (dict_iter(env->sc_ca_dict, &iter_dict, &k, (void **)&sca)) {
+ log_debug("info: loading CA information for %s", k);
+ if (!ssl_load_cafile(sca, sca->ca_cert_file))
+ fatalx("load_pki_tree: failed to load CA file");
+ }
+}
+
+void
+load_pki_keys(void)
+{
+ struct pki *pki;
+ const char *k;
+ void *iter_dict;
+
+ log_debug("debug: init ssl-tree");
+ iter_dict = NULL;
+ while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) {
+ log_debug("info: loading pki keys for %s", k);
+
+ if (!ssl_load_keyfile(pki, pki->pki_key_file, k))
+ fatalx("load_pki_keys: failed to load key file");
+ }
+}
+
+int
+fork_proc_backend(const char *key, const char *conf, const char *procname)
+{
+ pid_t pid;
+ int sp[2];
+ char path[PATH_MAX];
+ char name[PATH_MAX];
+ char *arg;
+
+ if (strlcpy(name, conf, sizeof(name)) >= sizeof(name)) {
+ log_warnx("warn: %s-proc: conf too long", key);
+ return (0);
+ }
+
+ arg = strchr(name, ':');
+ if (arg)
+ *arg++ = '\0';
+
+ if (snprintf(path, sizeof(path), PATH_LIBEXEC "/%s-%s", key, name) >=
+ (ssize_t)sizeof(path)) {
+ log_warn("warn: %s-proc: exec path too long", key);
+ return (-1);
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) {
+ log_warn("warn: %s-proc: socketpair", key);
+ return (-1);
+ }
+
+ if ((pid = fork()) == -1) {
+ log_warn("warn: %s-proc: fork", key);
+ close(sp[0]);
+ close(sp[1]);
+ return (-1);
+ }
+
+ if (pid == 0) {
+ /* child process */
+ dup2(sp[0], STDIN_FILENO);
+ closefrom(STDERR_FILENO + 1);
+
+ if (procname == NULL)
+ procname = name;
+
+ execl(path, procname, arg, (char *)NULL);
+ err(1, "execl: %s", path);
+ }
+
+ /* parent process */
+ close(sp[0]);
+
+ return (sp[1]);
+}
+
+struct child *
+child_add(pid_t pid, int type, const char *title)
+{
+ struct child *child;
+
+ if ((child = calloc(1, sizeof(*child))) == NULL)
+ fatal("smtpd: child_add: calloc");
+
+ child->pid = pid;
+ child->type = type;
+ child->title = title;
+
+ tree_xset(&children, pid, child);
+
+ return (child);
+}
+
+static void
+purge_task(void)
+{
+ struct passwd *pw;
+ DIR *d;
+ int n;
+ uid_t uid;
+ gid_t gid;
+
+ n = 0;
+ if ((d = opendir(PATH_SPOOL PATH_PURGE))) {
+ while (readdir(d) != NULL)
+ n++;
+ closedir(d);
+ } else
+ log_warn("warn: purge_task: opendir");
+
+ if (n > 2) {
+ switch (purge_pid = fork()) {
+ case -1:
+ log_warn("warn: purge_task: fork");
+ break;
+ case 0:
+ if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL)
+ fatalx("unknown user " SMTPD_QUEUE_USER);
+ if (chroot(PATH_SPOOL PATH_PURGE) == -1)
+ fatal("smtpd: chroot");
+ if (chdir("/") == -1)
+ fatal("smtpd: chdir");
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+ if (setgroups(1, &gid) ||
+ setresgid(gid, gid, gid) ||
+ setresuid(uid, uid, uid))
+ fatal("smtpd: cannot drop privileges");
+ rmtree("/", 1);
+ _exit(0);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static void
+fork_filter_processes(void)
+{
+ const char *name;
+ void *iter;
+ const char *fn;
+ struct filter_config *fc;
+ struct filter_config *fcs;
+ struct filter_proc *fp;
+ size_t i;
+
+ /* For each filter chain, assign the registered subsystem to subfilters */
+ iter = NULL;
+ while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) {
+ if (fc->chain) {
+ for (i = 0; i < fc->chain_size; ++i) {
+ fcs = dict_xget(env->sc_filters_dict, fc->chain[i]);
+ fcs->filter_subsystem |= fc->filter_subsystem;
+ }
+ }
+ }
+
+ /* For each filter, assign the registered subsystem to underlying proc */
+ iter = NULL;
+ while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) {
+ if (fc->proc) {
+ fp = dict_xget(env->sc_filter_processes_dict, fc->proc);
+ fp->filter_subsystem |= fc->filter_subsystem;
+ }
+ }
+
+ iter = NULL;
+ while (dict_iter(env->sc_filter_processes_dict, &iter, &name, (void **)&fp))
+ fork_filter_process(name, fp->command, fp->user, fp->group, fp->chroot, fp->filter_subsystem);
+}
+
+static void
+fork_filter_process(const char *name, const char *command, const char *user, const char *group, const char *chroot_path, uint32_t subsystems)
+{
+ pid_t pid;
+ struct filter_proc *processor;
+ char buf;
+ int sp[2], errfd[2];
+ struct passwd *pw;
+ struct group *gr;
+ char exec[_POSIX_ARG_MAX];
+ int execr;
+
+ if (user == NULL)
+ user = SMTPD_USER;
+ if ((pw = getpwnam(user)) == NULL)
+ err(1, "getpwnam");
+
+ if (group) {
+ if ((gr = getgrnam(group)) == NULL)
+ err(1, "getgrnam");
+ }
+ else {
+ if ((gr = getgrgid(pw->pw_gid)) == NULL)
+ err(1, "getgrgid");
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1)
+ err(1, "socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, errfd) == -1)
+ err(1, "socketpair");
+
+ if ((pid = fork()) == -1)
+ err(1, "fork");
+
+ /* parent passes the child fd over to lka */
+ if (pid > 0) {
+ processor = dict_xget(env->sc_filter_processes_dict, name);
+ processor->errfd = errfd[1];
+ child_add(pid, CHILD_PROCESSOR, name);
+ close(sp[0]);
+ close(errfd[0]);
+ m_create(p_lka, IMSG_LKA_PROCESSOR_FORK, 0, 0, sp[1]);
+ m_add_string(p_lka, name);
+ m_add_u32(p_lka, (uint32_t)subsystems);
+ m_close(p_lka);
+ return;
+ }
+
+ close(sp[1]);
+ close(errfd[1]);
+ dup2(sp[0], STDIN_FILENO);
+ dup2(sp[0], STDOUT_FILENO);
+ dup2(errfd[0], STDERR_FILENO);
+
+ if (chroot_path) {
+ if (chroot(chroot_path) != 0 || chdir("/") != 0)
+ err(1, "chroot: %s", chroot_path);
+ }
+
+ if (setgroups(1, &gr->gr_gid) ||
+ setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ err(1, "fork_filter_process: cannot drop privileges");
+
+ xclosefrom(STDERR_FILENO + 1);
+
+ if (setsid() < 0)
+ err(1, "setsid");
+ if (signal(SIGPIPE, SIG_DFL) == SIG_ERR ||
+ signal(SIGINT, SIG_DFL) == SIG_ERR ||
+ signal(SIGTERM, SIG_DFL) == SIG_ERR ||
+ signal(SIGCHLD, SIG_DFL) == SIG_ERR ||
+ signal(SIGHUP, SIG_DFL) == SIG_ERR)
+ err(1, "signal");
+
+ if (command[0] == '/')
+ execr = snprintf(exec, sizeof(exec), "exec %s", command);
+ else
+ execr = snprintf(exec, sizeof(exec), "exec %s/%s",
+ PATH_LIBEXEC, command);
+ if (execr >= (int) sizeof(exec))
+ errx(1, "%s: exec path too long", name);
+
+ /*
+ * Wait for lka to acknowledge that it received the fd.
+ * This prevents a race condition between the filter sending an error
+ * message, and exiting and lka not being able to log it because of
+ * SIGCHLD.
+ * (Ab)use read to determine if the fd is installed; since stderr is
+ * never going to be read from we can shutdown(2) the write-end in lka.
+ */
+ if (read(STDERR_FILENO, &buf, 1) != 0)
+ errx(1, "lka didn't properly close write end of error socket");
+ if (system(exec) == -1)
+ err(1, NULL);
+
+ /* there's no successful exit from a processor */
+ _exit(1);
+}
+
+static void
+forkmda(struct mproc *p, uint64_t id, struct deliver *deliver)
+{
+ char ebuf[128], sfn[32];
+ struct dispatcher *dsp;
+ struct child *child;
+ pid_t pid;
+ int allout, pipefd[2];
+ struct passwd *pw;
+ const char *pw_name;
+ uid_t pw_uid;
+ gid_t pw_gid;
+ const char *pw_dir;
+
+ dsp = dict_xget(env->sc_dispatchers, deliver->dispatcher);
+ if (dsp->type != DISPATCHER_LOCAL)
+ fatalx("non-local dispatcher called from forkmda()");
+
+ log_debug("debug: smtpd: forking mda for session %016"PRIx64
+ ": %s as %s", id, deliver->userinfo.username,
+ dsp->u.local.user ? dsp->u.local.user : deliver->userinfo.username);
+
+ if (dsp->u.local.user) {
+ if ((pw = getpwnam(dsp->u.local.user)) == NULL) {
+ (void)snprintf(ebuf, sizeof ebuf,
+ "delivery user '%s' does not exist",
+ dsp->u.local.user);
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1);
+ m_add_id(p_pony, id);
+ m_add_int(p_pony, MDA_PERMFAIL);
+ m_add_int(p_pony, EX_NOUSER);
+ m_add_string(p_pony, ebuf);
+ m_close(p_pony);
+ return;
+ }
+ pw_name = pw->pw_name;
+ pw_uid = pw->pw_uid;
+ pw_gid = pw->pw_gid;
+ pw_dir = pw->pw_dir;
+ }
+ else {
+ pw_name = deliver->userinfo.username;
+ pw_uid = deliver->userinfo.uid;
+ pw_gid = deliver->userinfo.gid;
+ pw_dir = deliver->userinfo.directory;
+ }
+
+ if (pw_uid == 0 && deliver->mda_exec[0]) {
+ pw_name = deliver->userinfo.username;
+ pw_uid = deliver->userinfo.uid;
+ pw_gid = deliver->userinfo.gid;
+ pw_dir = deliver->userinfo.directory;
+ }
+
+ if (pw_uid == 0 && !dsp->u.local.is_mbox) {
+ (void)snprintf(ebuf, sizeof ebuf, "not allowed to deliver to: %s",
+ deliver->userinfo.username);
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1);
+ m_add_id(p_pony, id);
+ m_add_int(p_pony, MDA_PERMFAIL);
+ m_add_int(p_pony, EX_NOPERM);
+ m_add_string(p_pony, ebuf);
+ m_close(p_pony);
+ return;
+ }
+
+ if (pipe(pipefd) == -1) {
+ (void)snprintf(ebuf, sizeof ebuf, "pipe: %s", strerror(errno));
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1);
+ m_add_id(p_pony, id);
+ m_add_int(p_pony, MDA_TEMPFAIL);
+ m_add_int(p_pony, EX_OSERR);
+ m_add_string(p_pony, ebuf);
+ m_close(p_pony);
+ return;
+ }
+
+ /* prepare file which captures stdout and stderr */
+ (void)strlcpy(sfn, "/tmp/smtpd.out.XXXXXXXXXXX", sizeof(sfn));
+ allout = mkstemp(sfn);
+ if (allout == -1) {
+ (void)snprintf(ebuf, sizeof ebuf, "mkstemp: %s", strerror(errno));
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1);
+ m_add_id(p_pony, id);
+ m_add_int(p_pony, MDA_TEMPFAIL);
+ m_add_int(p_pony, EX_OSERR);
+ m_add_string(p_pony, ebuf);
+ m_close(p_pony);
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return;
+ }
+ unlink(sfn);
+
+ pid = fork();
+ if (pid == -1) {
+ (void)snprintf(ebuf, sizeof ebuf, "fork: %s", strerror(errno));
+ m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1);
+ m_add_id(p_pony, id);
+ m_add_int(p_pony, MDA_TEMPFAIL);
+ m_add_int(p_pony, EX_OSERR);
+ m_add_string(p_pony, ebuf);
+ m_close(p_pony);
+ close(pipefd[0]);
+ close(pipefd[1]);
+ close(allout);
+ return;
+ }
+
+ /* parent passes the child fd over to mda */
+ if (pid > 0) {
+ child = child_add(pid, CHILD_MDA, NULL);
+ child->mda_out = allout;
+ child->mda_id = id;
+ close(pipefd[0]);
+ m_create(p, IMSG_MDA_FORK, 0, 0, pipefd[1]);
+ m_add_id(p, id);
+ m_close(p);
+ return;
+ }
+
+ /* mbox helper, create mailbox before privdrop if it doesn't exist */
+ if (dsp->u.local.is_mbox)
+ mda_mbox_init(deliver);
+
+ if (chdir(pw_dir) == -1 && chdir("/") == -1)
+ err(1, "chdir");
+ if (setgroups(1, &pw_gid) ||
+ setresgid(pw_gid, pw_gid, pw_gid) ||
+ setresuid(pw_uid, pw_uid, pw_uid))
+ err(1, "forkmda: cannot drop privileges");
+ if (dup2(pipefd[0], STDIN_FILENO) == -1 ||
+ dup2(allout, STDOUT_FILENO) == -1 ||
+ dup2(allout, STDERR_FILENO) == -1)
+ err(1, "forkmda: dup2");
+ closefrom(STDERR_FILENO + 1);
+ if (setsid() < 0)
+ err(1, "setsid");
+ if (signal(SIGPIPE, SIG_DFL) == SIG_ERR ||
+ signal(SIGINT, SIG_DFL) == SIG_ERR ||
+ signal(SIGTERM, SIG_DFL) == SIG_ERR ||
+ signal(SIGCHLD, SIG_DFL) == SIG_ERR ||
+ signal(SIGHUP, SIG_DFL) == SIG_ERR)
+ err(1, "signal");
+
+ /* avoid hangs by setting 5m timeout */
+ alarm(300);
+
+ if (dsp->u.local.is_mbox &&
+ dsp->u.local.mda_wrapper == NULL &&
+ deliver->mda_exec[0] == '\0')
+ mda_mbox(deliver);
+ else
+ mda_unpriv(dsp, deliver, pw_name, pw_dir);
+}
+
+static void
+offline_scan(int fd, short ev, void *arg)
+{
+ char *path_argv[2];
+ FTS *fts = arg;
+ FTSENT *e;
+ int n = 0;
+
+ path_argv[0] = PATH_SPOOL PATH_OFFLINE;
+ path_argv[1] = NULL;
+
+ if (fts == NULL) {
+ log_debug("debug: smtpd: scanning offline queue...");
+ fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+ if (fts == NULL) {
+ log_warn("fts_open: %s", path_argv[0]);
+ return;
+ }
+ }
+
+ while ((e = fts_read(fts)) != NULL) {
+ if (e->fts_info != FTS_F)
+ continue;
+
+ /* offline files must be at depth 1 */
+ if (e->fts_level != 1)
+ continue;
+
+ /* offline file group must match parent directory group */
+ if (e->fts_statp->st_gid != e->fts_parent->fts_statp->st_gid)
+ continue;
+
+ if (e->fts_statp->st_size == 0) {
+ if (unlink(e->fts_accpath) == -1)
+ log_warnx("warn: smtpd: could not unlink %s", e->fts_accpath);
+ continue;
+ }
+
+ if (offline_add(e->fts_name, e->fts_statp->st_uid,
+ e->fts_statp->st_gid)) {
+ log_warnx("warn: smtpd: "
+ "could not add offline message %s", e->fts_name);
+ continue;
+ }
+
+ if ((n++) == OFFLINE_READMAX) {
+ evtimer_set(&offline_ev, offline_scan, fts);
+ offline_timeout.tv_sec = 0;
+ offline_timeout.tv_usec = 100000;
+ evtimer_add(&offline_ev, &offline_timeout);
+ return;
+ }
+ }
+
+ log_debug("debug: smtpd: offline scanning done");
+ fts_close(fts);
+}
+
+static int
+offline_enqueue(char *name, uid_t uid, gid_t gid)
+{
+ char *path;
+ struct stat sb;
+ pid_t pid;
+ struct child *child;
+ struct passwd *pw;
+ int pathlen;
+
+ pathlen = asprintf(&path, "%s/%s", PATH_SPOOL PATH_OFFLINE, name);
+ if (pathlen == -1) {
+ log_warnx("warn: smtpd: asprintf");
+ return (-1);
+ }
+
+ if (pathlen >= PATH_MAX) {
+ log_warnx("warn: smtpd: pathname exceeds PATH_MAX");
+ free(path);
+ return (-1);
+ }
+
+ log_debug("debug: smtpd: enqueueing offline message %s", path);
+
+ if ((pid = fork()) == -1) {
+ log_warn("warn: smtpd: fork");
+ free(path);
+ return (-1);
+ }
+
+ if (pid == 0) {
+ char *envp[2], *p = NULL, *tmp;
+ int fd;
+ FILE *fp;
+ size_t sz = 0;
+ ssize_t len;
+ arglist args;
+
+ closefrom(STDERR_FILENO + 1);
+
+ memset(&args, 0, sizeof(args));
+
+ if ((fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) == -1) {
+ log_warn("warn: smtpd: open: %s", path);
+ _exit(1);
+ }
+
+ if (fstat(fd, &sb) == -1) {
+ log_warn("warn: smtpd: fstat: %s", path);
+ _exit(1);
+ }
+
+ if (!S_ISREG(sb.st_mode)) {
+ log_warnx("warn: smtpd: file %s (uid %d) not regular",
+ path, sb.st_uid);
+ _exit(1);
+ }
+
+ if (sb.st_nlink != 1) {
+ log_warnx("warn: smtpd: file %s is hard-link", path);
+ _exit(1);
+ }
+
+ if (sb.st_uid != uid) {
+ log_warnx("warn: smtpd: file %s has bad uid %d",
+ path, sb.st_uid);
+ _exit(1);
+ }
+
+ if (sb.st_gid != gid) {
+ log_warnx("warn: smtpd: file %s has bad gid %d",
+ path, sb.st_gid);
+ _exit(1);
+ }
+
+ pw = getpwuid(sb.st_uid);
+ if (pw == NULL) {
+ log_warnx("warn: smtpd: getpwuid for uid %d failed",
+ sb.st_uid);
+ _exit(1);
+ }
+
+ 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))
+ _exit(1);
+
+ if ((fp = fdopen(fd, "r")) == NULL)
+ _exit(1);
+
+ if (chdir(pw->pw_dir) == -1 && chdir("/") == -1)
+ _exit(1);
+
+ if (setsid() == -1 ||
+ signal(SIGPIPE, SIG_DFL) == SIG_ERR ||
+ dup2(fileno(fp), STDIN_FILENO) == -1)
+ _exit(1);
+
+ if ((len = getline(&p, &sz, fp)) == -1)
+ _exit(1);
+
+ if (p[len - 1] != '\n')
+ _exit(1);
+ p[len - 1] = '\0';
+
+ addargs(&args, "%s", "sendmail");
+ addargs(&args, "%s", "-S");
+
+ while ((tmp = strsep(&p, "|")) != NULL)
+ addargs(&args, "%s", tmp);
+
+ free(p);
+ if (lseek(fileno(fp), len, SEEK_SET) == -1)
+ _exit(1);
+
+ envp[0] = "PATH=" _PATH_DEFPATH;
+ envp[1] = (char *)NULL;
+ environ = envp;
+
+ execvp(PATH_SMTPCTL, args.list);
+ _exit(1);
+ }
+
+ offline_running++;
+ child = child_add(pid, CHILD_ENQUEUE_OFFLINE, NULL);
+ child->path = path;
+
+ return (0);
+}
+
+static int
+offline_add(char *path, uid_t uid, gid_t gid)
+{
+ struct offline *q;
+
+ if (offline_running < OFFLINE_QUEUEMAX)
+ /* skip queue */
+ return offline_enqueue(path, uid, gid);
+
+ q = malloc(sizeof(*q) + strlen(path) + 1);
+ if (q == NULL)
+ return (-1);
+ q->uid = uid;
+ q->gid = gid;
+ q->path = (char *)q + sizeof(*q);
+ memmove(q->path, path, strlen(path) + 1);
+ TAILQ_INSERT_TAIL(&offline_q, q, entry);
+
+ return (0);
+}
+
+static void
+offline_done(void)
+{
+ struct offline *q;
+
+ offline_running--;
+
+ while (offline_running < OFFLINE_QUEUEMAX) {
+ if ((q = TAILQ_FIRST(&offline_q)) == NULL)
+ break; /* all done */
+ TAILQ_REMOVE(&offline_q, q, entry);
+ offline_enqueue(q->path, q->uid, q->gid);
+ free(q);
+ }
+}
+
+static int
+parent_forward_open(char *username, char *directory, uid_t uid, gid_t gid)
+{
+ char pathname[PATH_MAX];
+ int fd;
+ struct stat sb;
+
+ if (!bsnprintf(pathname, sizeof (pathname), "%s/.forward",
+ directory)) {
+ log_warnx("warn: smtpd: %s: pathname too large", pathname);
+ return -1;
+ }
+
+ if (stat(directory, &sb) == -1) {
+ log_warn("warn: smtpd: parent_forward_open: %s", directory);
+ return -1;
+ }
+ if (sb.st_mode & S_ISVTX) {
+ log_warnx("warn: smtpd: parent_forward_open: %s is sticky",
+ directory);
+ errno = EAGAIN;
+ return -1;
+ }
+
+ do {
+ fd = open(pathname, O_RDONLY|O_NOFOLLOW|O_NONBLOCK);
+ } while (fd == -1 && errno == EINTR);
+ if (fd == -1) {
+ if (errno == ENOENT)
+ return -1;
+ if (errno == EMFILE || errno == ENFILE || errno == EIO) {
+ errno = EAGAIN;
+ return -1;
+ }
+ if (errno == ELOOP)
+ log_warnx("warn: smtpd: parent_forward_open: %s: "
+ "cannot follow symbolic links", pathname);
+ else
+ log_warn("warn: smtpd: parent_forward_open: %s", pathname);
+ return -1;
+ }
+
+ if (!secure_file(fd, pathname, directory, uid, 1)) {
+ log_warnx("warn: smtpd: %s: unsecure file", pathname);
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+void
+imsg_dispatch(struct mproc *p, struct imsg *imsg)
+{
+ struct timespec t0, t1, dt;
+ int msg;
+
+ if (imsg == NULL) {
+ imsg_callback(p, imsg);
+ return;
+ }
+
+ log_imsg(smtpd_process, p->proc, imsg);
+
+ if (profiling & PROFILE_IMSG)
+ clock_gettime(CLOCK_MONOTONIC, &t0);
+
+ msg = imsg->hdr.type;
+ imsg_callback(p, imsg);
+
+ if (profiling & PROFILE_IMSG) {
+ clock_gettime(CLOCK_MONOTONIC, &t1);
+ timespecsub(&t1, &t0, &dt);
+
+ log_debug("profile-imsg: %s %s %s %d %lld.%09ld",
+ proc_name(smtpd_process),
+ proc_name(p->proc),
+ imsg_to_str(msg),
+ (int)imsg->hdr.len,
+ (long long)dt.tv_sec,
+ dt.tv_nsec);
+
+ if (profiling & PROFILE_TOSTAT) {
+ char key[STAT_KEY_SIZE];
+ /* can't profstat control process yet */
+ if (smtpd_process == PROC_CONTROL)
+ return;
+
+ if (!bsnprintf(key, sizeof key,
+ "profiling.imsg.%s.%s.%s",
+ proc_name(smtpd_process),
+ proc_name(p->proc),
+ imsg_to_str(msg)))
+ return;
+ stat_set(key, stat_timespec(&dt));
+ }
+ }
+}
+
+void
+log_imsg(int to, int from, struct imsg *imsg)
+{
+
+ if (to == PROC_CONTROL && imsg->hdr.type == IMSG_STAT_SET)
+ return;
+
+ if (imsg->fd != -1)
+ log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu, fd=%d)",
+ proc_name(to),
+ proc_name(from),
+ imsg_to_str(imsg->hdr.type),
+ imsg->hdr.len - IMSG_HEADER_SIZE,
+ imsg->fd);
+ else
+ log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu)",
+ proc_name(to),
+ proc_name(from),
+ imsg_to_str(imsg->hdr.type),
+ imsg->hdr.len - IMSG_HEADER_SIZE);
+}
+
+const char *
+proc_title(enum smtp_proc_type proc)
+{
+ switch (proc) {
+ case PROC_PARENT:
+ return "[priv]";
+ case PROC_LKA:
+ return "lookup";
+ case PROC_QUEUE:
+ return "queue";
+ case PROC_CONTROL:
+ return "control";
+ case PROC_SCHEDULER:
+ return "scheduler";
+ case PROC_PONY:
+ return "pony express";
+ case PROC_CA:
+ return "klondike";
+ case PROC_CLIENT:
+ return "client";
+ case PROC_PROCESSOR:
+ return "processor";
+ }
+ return "unknown";
+}
+
+const char *
+proc_name(enum smtp_proc_type proc)
+{
+ switch (proc) {
+ case PROC_PARENT:
+ return "parent";
+ case PROC_LKA:
+ return "lka";
+ case PROC_QUEUE:
+ return "queue";
+ case PROC_CONTROL:
+ return "control";
+ case PROC_SCHEDULER:
+ return "scheduler";
+ case PROC_PONY:
+ return "pony";
+ case PROC_CA:
+ return "ca";
+ case PROC_CLIENT:
+ return "client-proc";
+ default:
+ return "unknown";
+ }
+}
+
+#define CASE(x) case x : return #x
+
+const char *
+imsg_to_str(int type)
+{
+ static char buf[32];
+
+ switch (type) {
+ CASE(IMSG_NONE);
+
+ CASE(IMSG_CTL_OK);
+ CASE(IMSG_CTL_FAIL);
+
+ CASE(IMSG_CTL_GET_DIGEST);
+ CASE(IMSG_CTL_GET_STATS);
+ CASE(IMSG_CTL_LIST_MESSAGES);
+ CASE(IMSG_CTL_LIST_ENVELOPES);
+ CASE(IMSG_CTL_MTA_SHOW_HOSTS);
+ CASE(IMSG_CTL_MTA_SHOW_RELAYS);
+ CASE(IMSG_CTL_MTA_SHOW_ROUTES);
+ CASE(IMSG_CTL_MTA_SHOW_HOSTSTATS);
+ CASE(IMSG_CTL_MTA_BLOCK);
+ CASE(IMSG_CTL_MTA_UNBLOCK);
+ CASE(IMSG_CTL_MTA_SHOW_BLOCK);
+ CASE(IMSG_CTL_PAUSE_EVP);
+ CASE(IMSG_CTL_PAUSE_MDA);
+ CASE(IMSG_CTL_PAUSE_MTA);
+ CASE(IMSG_CTL_PAUSE_SMTP);
+ CASE(IMSG_CTL_PROFILE);
+ CASE(IMSG_CTL_PROFILE_DISABLE);
+ CASE(IMSG_CTL_PROFILE_ENABLE);
+ CASE(IMSG_CTL_RESUME_EVP);
+ CASE(IMSG_CTL_RESUME_MDA);
+ CASE(IMSG_CTL_RESUME_MTA);
+ CASE(IMSG_CTL_RESUME_SMTP);
+ CASE(IMSG_CTL_RESUME_ROUTE);
+ CASE(IMSG_CTL_REMOVE);
+ CASE(IMSG_CTL_SCHEDULE);
+ CASE(IMSG_CTL_SHOW_STATUS);
+ CASE(IMSG_CTL_TRACE_DISABLE);
+ CASE(IMSG_CTL_TRACE_ENABLE);
+ CASE(IMSG_CTL_UPDATE_TABLE);
+ CASE(IMSG_CTL_VERBOSE);
+ CASE(IMSG_CTL_DISCOVER_EVPID);
+ CASE(IMSG_CTL_DISCOVER_MSGID);
+
+ CASE(IMSG_CTL_SMTP_SESSION);
+
+ CASE(IMSG_GETADDRINFO);
+ CASE(IMSG_GETADDRINFO_END);
+ CASE(IMSG_GETNAMEINFO);
+ CASE(IMSG_RES_QUERY);
+
+ CASE(IMSG_CERT_INIT);
+ CASE(IMSG_CERT_CERTIFICATE);
+ CASE(IMSG_CERT_VERIFY);
+
+ CASE(IMSG_SETUP_KEY);
+ CASE(IMSG_SETUP_PEER);
+ CASE(IMSG_SETUP_DONE);
+
+ CASE(IMSG_CONF_START);
+ CASE(IMSG_CONF_END);
+
+ CASE(IMSG_STAT_INCREMENT);
+ CASE(IMSG_STAT_DECREMENT);
+ CASE(IMSG_STAT_SET);
+
+ CASE(IMSG_LKA_AUTHENTICATE);
+ CASE(IMSG_LKA_OPEN_FORWARD);
+ CASE(IMSG_LKA_ENVELOPE_SUBMIT);
+ CASE(IMSG_LKA_ENVELOPE_COMMIT);
+
+ CASE(IMSG_QUEUE_DELIVER);
+ CASE(IMSG_QUEUE_DELIVERY_OK);
+ CASE(IMSG_QUEUE_DELIVERY_TEMPFAIL);
+ CASE(IMSG_QUEUE_DELIVERY_PERMFAIL);
+ CASE(IMSG_QUEUE_DELIVERY_LOOP);
+ CASE(IMSG_QUEUE_DISCOVER_EVPID);
+ CASE(IMSG_QUEUE_DISCOVER_MSGID);
+ CASE(IMSG_QUEUE_ENVELOPE_ACK);
+ CASE(IMSG_QUEUE_ENVELOPE_COMMIT);
+ CASE(IMSG_QUEUE_ENVELOPE_REMOVE);
+ CASE(IMSG_QUEUE_ENVELOPE_SCHEDULE);
+ CASE(IMSG_QUEUE_ENVELOPE_SUBMIT);
+ CASE(IMSG_QUEUE_HOLDQ_HOLD);
+ CASE(IMSG_QUEUE_HOLDQ_RELEASE);
+ CASE(IMSG_QUEUE_MESSAGE_COMMIT);
+ CASE(IMSG_QUEUE_MESSAGE_ROLLBACK);
+ CASE(IMSG_QUEUE_SMTP_SESSION);
+ CASE(IMSG_QUEUE_TRANSFER);
+
+ CASE(IMSG_MDA_DELIVERY_OK);
+ CASE(IMSG_MDA_DELIVERY_TEMPFAIL);
+ CASE(IMSG_MDA_DELIVERY_PERMFAIL);
+ CASE(IMSG_MDA_DELIVERY_LOOP);
+ CASE(IMSG_MDA_DELIVERY_HOLD);
+ CASE(IMSG_MDA_DONE);
+ CASE(IMSG_MDA_FORK);
+ CASE(IMSG_MDA_HOLDQ_RELEASE);
+ CASE(IMSG_MDA_LOOKUP_USERINFO);
+ CASE(IMSG_MDA_KILL);
+ CASE(IMSG_MDA_OPEN_MESSAGE);
+
+ CASE(IMSG_MTA_DELIVERY_OK);
+ CASE(IMSG_MTA_DELIVERY_TEMPFAIL);
+ CASE(IMSG_MTA_DELIVERY_PERMFAIL);
+ CASE(IMSG_MTA_DELIVERY_LOOP);
+ CASE(IMSG_MTA_DELIVERY_HOLD);
+ CASE(IMSG_MTA_DNS_HOST);
+ CASE(IMSG_MTA_DNS_HOST_END);
+ CASE(IMSG_MTA_DNS_MX);
+ CASE(IMSG_MTA_DNS_MX_PREFERENCE);
+ CASE(IMSG_MTA_HOLDQ_RELEASE);
+ CASE(IMSG_MTA_LOOKUP_CREDENTIALS);
+ CASE(IMSG_MTA_LOOKUP_SOURCE);
+ CASE(IMSG_MTA_LOOKUP_HELO);
+ CASE(IMSG_MTA_LOOKUP_SMARTHOST);
+ CASE(IMSG_MTA_OPEN_MESSAGE);
+ CASE(IMSG_MTA_SCHEDULE);
+
+ CASE(IMSG_SCHED_ENVELOPE_BOUNCE);
+ CASE(IMSG_SCHED_ENVELOPE_DELIVER);
+ CASE(IMSG_SCHED_ENVELOPE_EXPIRE);
+ CASE(IMSG_SCHED_ENVELOPE_INJECT);
+ CASE(IMSG_SCHED_ENVELOPE_REMOVE);
+ CASE(IMSG_SCHED_ENVELOPE_TRANSFER);
+
+ CASE(IMSG_SMTP_AUTHENTICATE);
+ CASE(IMSG_SMTP_MESSAGE_COMMIT);
+ CASE(IMSG_SMTP_MESSAGE_CREATE);
+ CASE(IMSG_SMTP_MESSAGE_ROLLBACK);
+ CASE(IMSG_SMTP_MESSAGE_OPEN);
+ CASE(IMSG_SMTP_CHECK_SENDER);
+ CASE(IMSG_SMTP_EXPAND_RCPT);
+ CASE(IMSG_SMTP_LOOKUP_HELO);
+
+ CASE(IMSG_SMTP_REQ_CONNECT);
+ CASE(IMSG_SMTP_REQ_HELO);
+ CASE(IMSG_SMTP_REQ_MAIL);
+ CASE(IMSG_SMTP_REQ_RCPT);
+ CASE(IMSG_SMTP_REQ_DATA);
+ CASE(IMSG_SMTP_REQ_EOM);
+ CASE(IMSG_SMTP_EVENT_RSET);
+ CASE(IMSG_SMTP_EVENT_COMMIT);
+ CASE(IMSG_SMTP_EVENT_ROLLBACK);
+ CASE(IMSG_SMTP_EVENT_DISCONNECT);
+
+ CASE(IMSG_LKA_PROCESSOR_FORK);
+ CASE(IMSG_LKA_PROCESSOR_ERRFD);
+
+ CASE(IMSG_REPORT_SMTP_LINK_CONNECT);
+ CASE(IMSG_REPORT_SMTP_LINK_DISCONNECT);
+ CASE(IMSG_REPORT_SMTP_LINK_TLS);
+ CASE(IMSG_REPORT_SMTP_LINK_GREETING);
+ CASE(IMSG_REPORT_SMTP_LINK_IDENTIFY);
+ CASE(IMSG_REPORT_SMTP_LINK_AUTH);
+
+ CASE(IMSG_REPORT_SMTP_TX_RESET);
+ CASE(IMSG_REPORT_SMTP_TX_BEGIN);
+ CASE(IMSG_REPORT_SMTP_TX_ENVELOPE);
+ CASE(IMSG_REPORT_SMTP_TX_COMMIT);
+ CASE(IMSG_REPORT_SMTP_TX_ROLLBACK);
+
+ CASE(IMSG_REPORT_SMTP_PROTOCOL_CLIENT);
+ CASE(IMSG_REPORT_SMTP_PROTOCOL_SERVER);
+
+ CASE(IMSG_FILTER_SMTP_BEGIN);
+ CASE(IMSG_FILTER_SMTP_END);
+ CASE(IMSG_FILTER_SMTP_PROTOCOL);
+ CASE(IMSG_FILTER_SMTP_DATA_BEGIN);
+ CASE(IMSG_FILTER_SMTP_DATA_END);
+
+ CASE(IMSG_CA_RSA_PRIVENC);
+ CASE(IMSG_CA_RSA_PRIVDEC);
+ CASE(IMSG_CA_ECDSA_SIGN);
+ default:
+ (void)snprintf(buf, sizeof(buf), "IMSG_??? (%d)", type);
+
+ return buf;
+ }
+}
+
+#ifdef BSD_AUTH
+int
+parent_auth_bsd(const char *username, const char *password)
+{
+ char user[LOGIN_NAME_MAX];
+ char pass[LINE_MAX];
+ int ret;
+
+ (void)strlcpy(user, username, sizeof(user));
+ (void)strlcpy(pass, password, sizeof(pass));
+
+ ret = auth_userokay(user, NULL, "auth-smtp", pass);
+ if (ret)
+ return LKA_OK;
+ return LKA_PERMFAIL;
+}
+#endif
+
+#ifdef USE_PAM
+int
+pam_conv_password(int num_msg, const struct pam_message **msg,
+ struct pam_response **respp, void *password)
+{
+ struct pam_response *response;
+
+ if (num_msg != 1)
+ return PAM_CONV_ERR;
+
+ response = calloc(1, sizeof(struct pam_response));
+ if (response == NULL || (response->resp = strdup(password)) == NULL) {
+ free(response);
+ return PAM_BUF_ERR;
+ }
+
+ *respp = response;
+ return PAM_SUCCESS;
+}
+int
+parent_auth_pam(const char *username, const char *password)
+{
+ int rc;
+ pam_handle_t *pamh = NULL;
+ struct pam_conv conv = { pam_conv_password, (char *)password };
+
+ if ((rc = pam_start(USE_PAM_SERVICE, username, &conv, &pamh)) != PAM_SUCCESS)
+ goto end;
+ if ((rc = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
+ goto end;
+ if ((rc = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
+ goto end;
+
+end:
+ pam_end(pamh, rc);
+
+ switch (rc) {
+ case PAM_SUCCESS:
+ return LKA_OK;
+ case PAM_SYSTEM_ERR:
+ case PAM_ABORT:
+ case PAM_AUTHINFO_UNAVAIL:
+ return LKA_TEMPFAIL;
+ default:
+ return LKA_PERMFAIL;
+ }
+}
+#endif
+
+#ifdef HAVE_GETSPNAM
+int
+parent_auth_getspnam(const char *username, const char *password)
+{
+ struct spwd *pw;
+ char *ep;
+
+ errno = 0;
+ do {
+ pw = getspnam(username);
+ } while (pw == NULL && errno == EINTR);
+
+ if (pw == NULL) {
+ if (errno)
+ return LKA_TEMPFAIL;
+ return LKA_PERMFAIL;
+ }
+
+ if ((ep = crypt(password, pw->sp_pwdp)) == NULL)
+ return LKA_PERMFAIL;
+
+ if (strcmp(pw->sp_pwdp, ep) == 0)
+ return LKA_OK;
+
+ return LKA_PERMFAIL;
+}
+#endif
+
+int
+parent_auth_pwd(const char *username, const char *password)
+{
+ struct passwd *pw;
+ char *ep;
+
+ errno = 0;
+ do {
+ pw = getpwnam(username);
+ } while (pw == NULL && errno == EINTR);
+
+ if (pw == NULL) {
+ if (errno)
+ return LKA_TEMPFAIL;
+ return LKA_PERMFAIL;
+ }
+
+ if ((ep = crypt(password, pw->pw_passwd)) == NULL)
+ return LKA_PERMFAIL;
+
+ if (strcmp(pw->pw_passwd, ep) == 0)
+ return LKA_OK;
+
+ return LKA_PERMFAIL;
+}
+
+int
+parent_auth_user(const char *username, const char *password)
+{
+#if defined(BSD_AUTH)
+ return (parent_auth_bsd(username, password));
+#elif defined(USE_PAM)
+ return (parent_auth_pam(username, password));
+#elif defined(HAVE_GETSPNAM)
+ return (parent_auth_getspnam(username, password));
+#else
+ return (parent_auth_pwd(username, password));
+#endif
+}