aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/smtpctl.c
diff options
context:
space:
mode:
authorGilles Chehade <gilles@poolp.org>2020-05-22 14:35:02 +0200
committerGilles Chehade <gilles@poolp.org>2020-05-22 14:35:02 +0200
commit90087f07a7cfffe10b4958e13558a0052f85691d (patch)
treebf0aac44374cfd5cd0da91cc67a6744decd74665 /smtpd/smtpctl.c
parentmoving smtpd to usr.sbin/smtpd to ease cherry-picking of upstream (diff)
downloadOpenSMTPD-90087f07a7cfffe10b4958e13558a0052f85691d.tar.xz
OpenSMTPD-90087f07a7cfffe10b4958e13558a0052f85691d.zip
Revert "moving smtpd to usr.sbin/smtpd to ease cherry-picking of upstream"
This reverts commit 90620a574d8824e5b2aa18709f2d5b5b6bb3cb38.
Diffstat (limited to 'smtpd/smtpctl.c')
-rw-r--r--smtpd/smtpctl.c1469
1 files changed, 1469 insertions, 0 deletions
diff --git a/smtpd/smtpctl.c b/smtpd/smtpctl.c
new file mode 100644
index 00000000..7dba4224
--- /dev/null
+++ b/smtpd/smtpctl.c
@@ -0,0 +1,1469 @@
+/* $OpenBSD: smtpctl.c,v 1.167 2020/02/24 16:16:07 millert Exp $ */
+
+/*
+ * Copyright (c) 2013 Eric Faurot <eric@openbsd.org>
+ * Copyright (c) 2006 Gilles Chehade <gilles@poolp.org>
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <net/if.h>
+/* #include <net/if_media.h> */
+/* #include <net/if_types.h> */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fts.h>
+#include <grp.h>
+#include <imsg.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS)
+#include <vis.h>
+#else
+#include "bsd-vis.h"
+#endif
+#include <limits.h>
+
+#include "smtpd.h"
+#include "parser.h"
+#include "log.h"
+
+#ifndef PATH_GZCAT
+#define PATH_GZCAT "/usr/bin/gzcat"
+#endif
+#define PATH_CAT "/bin/cat"
+#define PATH_QUEUE "/queue"
+#ifndef PATH_ENCRYPT
+#define PATH_ENCRYPT "/usr/bin/encrypt"
+#endif
+
+#ifndef HAVE_DB_API
+#define makemap(x, y, z) 1
+#endif
+
+
+int srv_connect(void);
+int srv_connected(void);
+
+void usage(void);
+static void show_queue_envelope(struct envelope *, int);
+static void getflag(uint *, int, char *, char *, size_t);
+static void display(const char *);
+static int str_to_trace(const char *);
+static int str_to_profile(const char *);
+static void show_offline_envelope(uint64_t);
+static int is_gzip_fp(FILE *);
+static int is_encrypted_fp(FILE *);
+static int is_encrypted_buffer(const char *);
+static int is_gzip_buffer(const char *);
+static FILE *offline_file(void);
+static void sendmail_compat(int, char **);
+
+extern int spfwalk(int, struct parameter *);
+
+extern char *__progname;
+int sendmail;
+struct smtpd *env;
+struct imsgbuf *ibuf;
+struct imsg imsg;
+char *rdata;
+size_t rlen;
+time_t now;
+
+struct queue_backend queue_backend_null;
+struct queue_backend queue_backend_proc;
+struct queue_backend queue_backend_ram;
+
+__dead void
+usage(void)
+{
+ if (sendmail)
+ fprintf(stderr, "usage: %s [-tv] [-f from] [-F name] to ...\n",
+ __progname);
+ else
+ fprintf(stderr, "usage: %s command [argument ...]\n",
+ __progname);
+ exit(1);
+}
+
+void stat_increment(const char *k, size_t v)
+{
+}
+
+void stat_decrement(const char *k, size_t v)
+{
+}
+
+int
+srv_connect(void)
+{
+ struct sockaddr_un s_un;
+ int ctl_sock, saved_errno;
+
+ /* connect to smtpd control socket */
+ if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ err(1, "socket");
+
+ memset(&s_un, 0, sizeof(s_un));
+ s_un.sun_family = AF_UNIX;
+ (void)strlcpy(s_un.sun_path, SMTPD_SOCKET, sizeof(s_un.sun_path));
+ if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) {
+ saved_errno = errno;
+ close(ctl_sock);
+ errno = saved_errno;
+ return (0);
+ }
+
+ ibuf = xcalloc(1, sizeof(struct imsgbuf));
+ imsg_init(ibuf, ctl_sock);
+
+ return (1);
+}
+
+int
+srv_connected(void)
+{
+ return ibuf != NULL ? 1 : 0;
+}
+
+FILE *
+offline_file(void)
+{
+ char path[PATH_MAX];
+ int fd;
+ FILE *fp;
+
+ if (!bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL,
+ PATH_OFFLINE, (long long int) time(NULL)))
+ err(EX_UNAVAILABLE, "snprintf");
+
+ if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
+ if (fd != -1)
+ unlink(path);
+ err(EX_UNAVAILABLE, "cannot create temporary file %s", path);
+ }
+
+ if (fchmod(fd, 0600) == -1) {
+ unlink(path);
+ err(EX_SOFTWARE, "fchmod");
+ }
+
+ return fp;
+}
+
+
+static void
+srv_flush(void)
+{
+ if (imsg_flush(ibuf) == -1)
+ err(1, "write error");
+}
+
+static void
+srv_send(int msg, const void *data, size_t len)
+{
+ if (ibuf == NULL && !srv_connect())
+ errx(1, "smtpd doesn't seem to be running");
+ imsg_compose(ibuf, msg, IMSG_VERSION, 0, -1, data, len);
+}
+
+static void
+srv_recv(int type)
+{
+ ssize_t n;
+
+ srv_flush();
+
+ while (1) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n) {
+ if (imsg.hdr.type == IMSG_CTL_FAIL &&
+ imsg.hdr.peerid != 0 &&
+ imsg.hdr.peerid != IMSG_VERSION)
+ errx(1, "incompatible smtpctl and smtpd");
+ if (type != -1 && type != (int)imsg.hdr.type)
+ errx(1, "bad message type");
+ rdata = imsg.data;
+ rlen = imsg.hdr.len - sizeof(imsg.hdr);
+ break;
+ }
+
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+ }
+}
+
+static void
+srv_read(void *dst, size_t sz)
+{
+ if (sz == 0)
+ return;
+ if (rlen < sz)
+ errx(1, "message too short");
+ if (dst)
+ memmove(dst, rdata, sz);
+ rlen -= sz;
+ rdata += sz;
+}
+
+static void
+srv_get_int(int *i)
+{
+ srv_read(i, sizeof(*i));
+}
+
+static void
+srv_get_time(time_t *t)
+{
+ srv_read(t, sizeof(*t));
+}
+
+static void
+srv_get_evpid(uint64_t *evpid)
+{
+ srv_read(evpid, sizeof(*evpid));
+}
+
+static void
+srv_get_string(const char **s)
+{
+ const char *end;
+ size_t len;
+
+ if (rlen == 0)
+ errx(1, "message too short");
+
+ rlen -= 1;
+ if (*rdata++ == '\0') {
+ *s = NULL;
+ return;
+ }
+
+ if (rlen == 0)
+ errx(1, "bogus string");
+
+ end = memchr(rdata, 0, rlen);
+ if (end == NULL)
+ errx(1, "unterminated string");
+
+ len = end + 1 - rdata;
+
+ *s = rdata;
+ rlen -= len;
+ rdata += len;
+}
+
+static void
+srv_get_envelope(struct envelope *evp)
+{
+ uint64_t evpid;
+ const char *str;
+
+ srv_get_evpid(&evpid);
+ srv_get_string(&str);
+
+ envelope_load_buffer(evp, str, strlen(str));
+ evp->id = evpid;
+}
+
+static void
+srv_end(void)
+{
+ if (rlen)
+ errx(1, "bogus data");
+ imsg_free(&imsg);
+}
+
+static int
+srv_check_result(int verbose_)
+{
+ srv_recv(-1);
+ srv_end();
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_OK:
+ if (verbose_)
+ printf("command succeeded\n");
+ return (0);
+ case IMSG_CTL_FAIL:
+ if (verbose_) {
+ if (rlen)
+ printf("command failed: %s\n", rdata);
+ else
+ printf("command failed\n");
+ }
+ return (1);
+ default:
+ errx(1, "wrong message in response: %u", imsg.hdr.type);
+ }
+ return (0);
+}
+
+static int
+srv_iter_messages(uint32_t *res)
+{
+ static uint32_t *msgids = NULL, from = 0;
+ static size_t n, curr;
+ static int done = 0;
+
+ if (done)
+ return (0);
+
+ if (msgids == NULL) {
+ srv_send(IMSG_CTL_LIST_MESSAGES, &from, sizeof(from));
+ srv_recv(IMSG_CTL_LIST_MESSAGES);
+ if (rlen == 0) {
+ srv_end();
+ done = 1;
+ return (0);
+ }
+ msgids = malloc(rlen);
+ n = rlen / sizeof(*msgids);
+ srv_read(msgids, rlen);
+ srv_end();
+
+ curr = 0;
+ from = msgids[n - 1] + 1;
+ if (from == 0)
+ done = 1;
+ }
+
+ *res = msgids[curr++];
+ if (curr == n) {
+ free(msgids);
+ msgids = NULL;
+ }
+
+ return (1);
+}
+
+static int
+srv_iter_envelopes(uint32_t msgid, struct envelope *evp)
+{
+ static uint32_t currmsgid = 0;
+ static uint64_t from = 0;
+ static int done = 0, need_send = 1, found;
+ int flags;
+ time_t nexttry;
+
+ if (currmsgid != msgid) {
+ if (currmsgid != 0 && !done)
+ errx(1, "must finish current iteration first");
+ currmsgid = msgid;
+ from = msgid_to_evpid(msgid);
+ done = 0;
+ found = 0;
+ need_send = 1;
+ }
+
+ if (done)
+ return (0);
+
+ again:
+ if (need_send) {
+ found = 0;
+ srv_send(IMSG_CTL_LIST_ENVELOPES, &from, sizeof(from));
+ }
+ need_send = 0;
+
+ srv_recv(IMSG_CTL_LIST_ENVELOPES);
+ if (rlen == 0) {
+ srv_end();
+ if (!found || evpid_to_msgid(from) != msgid) {
+ done = 1;
+ return (0);
+ }
+ need_send = 1;
+ goto again;
+ }
+
+ srv_get_int(&flags);
+ srv_get_time(&nexttry);
+ srv_get_envelope(evp);
+ srv_end();
+
+ evp->flags |= flags;
+ evp->nexttry = nexttry;
+
+ from = evp->id + 1;
+ found++;
+ return (1);
+}
+
+static int
+srv_iter_evpids(uint32_t msgid, uint64_t *evpid, int *offset)
+{
+ static uint64_t *evpids = NULL, *tmp;
+ static int n, tmpalloc, alloc = 0;
+ struct envelope evp;
+
+ if (*offset == 0) {
+ n = 0;
+ while (srv_iter_envelopes(msgid, &evp)) {
+ if (n == alloc) {
+ tmpalloc = alloc ? (alloc * 2) : 128;
+ tmp = recallocarray(evpids, alloc, tmpalloc,
+ sizeof(*evpids));
+ if (tmp == NULL)
+ err(1, "recallocarray");
+ evpids = tmp;
+ alloc = tmpalloc;
+ }
+ evpids[n++] = evp.id;
+ }
+ }
+
+ if (*offset >= n)
+ return (0);
+ *evpid = evpids[*offset];
+ *offset += 1;
+ return (1);
+}
+
+static void
+srv_foreach_envelope(struct parameter *argv, int ctl, size_t *total, size_t *ok)
+{
+ uint32_t msgid;
+ uint64_t evpid;
+ int i;
+
+ *total = 0;
+ *ok = 0;
+
+ if (argv == NULL) {
+ while (srv_iter_messages(&msgid)) {
+ i = 0;
+ while (srv_iter_evpids(msgid, &evpid, &i)) {
+ *total += 1;
+ srv_send(ctl, &evpid, sizeof(evpid));
+ if (srv_check_result(0) == 0)
+ *ok += 1;
+ }
+ }
+ } else if (argv->type == P_MSGID) {
+ i = 0;
+ while (srv_iter_evpids(argv->u.u_msgid, &evpid, &i)) {
+ srv_send(ctl, &evpid, sizeof(evpid));
+ if (srv_check_result(0) == 0)
+ *ok += 1;
+ }
+ } else {
+ *total += 1;
+ srv_send(ctl, &argv->u.u_evpid, sizeof(evpid));
+ if (srv_check_result(0) == 0)
+ *ok += 1;
+ }
+}
+
+static void
+srv_show_cmd(int cmd, const void *data, size_t len)
+{
+ int done = 0;
+
+ srv_send(cmd, data, len);
+
+ do {
+ srv_recv(cmd);
+ if (rlen) {
+ printf("%s\n", rdata);
+ srv_read(NULL, rlen);
+ }
+ else
+ done = 1;
+ srv_end();
+ } while (!done);
+}
+
+static void
+droppriv(void)
+{
+ struct passwd *pw;
+
+ if (geteuid())
+ return;
+
+ if ((pw = getpwnam(SMTPD_USER)) == NULL)
+ errx(1, "unknown user " SMTPD_USER);
+
+ 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)))
+ err(1, "cannot drop privileges");
+}
+
+static int
+do_permission_denied(int argc, struct parameter *argv)
+{
+ errx(1, "need root privileges");
+}
+
+static int
+do_log_brief(int argc, struct parameter *argv)
+{
+ int v = 0;
+
+ srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_log_verbose(int argc, struct parameter *argv)
+{
+ int v = TRACE_DEBUG;
+
+ srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_monitor(int argc, struct parameter *argv)
+{
+ struct stat_digest last, digest;
+ size_t count;
+
+ memset(&last, 0, sizeof(last));
+ count = 0;
+
+ while (1) {
+ srv_send(IMSG_CTL_GET_DIGEST, NULL, 0);
+ srv_recv(IMSG_CTL_GET_DIGEST);
+ srv_read(&digest, sizeof(digest));
+ srv_end();
+
+ if (count % 25 == 0) {
+ if (count != 0)
+ printf("\n");
+ printf("--- client --- "
+ "-- envelope -- "
+ "---- relay/delivery --- "
+ "------- misc -------\n"
+ "curr conn disc "
+ "curr enq deq "
+ "ok tmpfail prmfail loop "
+ "expire remove bounce\n");
+ }
+ printf("%4zu %4zu %4zu "
+ "%4zu %4zu %4zu "
+ "%4zu %4zu %4zu %4zu "
+ "%4zu %4zu %4zu\n",
+ digest.clt_connect - digest.clt_disconnect,
+ digest.clt_connect - last.clt_connect,
+ digest.clt_disconnect - last.clt_disconnect,
+
+ digest.evp_enqueued - digest.evp_dequeued,
+ digest.evp_enqueued - last.evp_enqueued,
+ digest.evp_dequeued - last.evp_dequeued,
+
+ digest.dlv_ok - last.dlv_ok,
+ digest.dlv_tempfail - last.dlv_tempfail,
+ digest.dlv_permfail - last.dlv_permfail,
+ digest.dlv_loop - last.dlv_loop,
+
+ digest.evp_expired - last.evp_expired,
+ digest.evp_removed - last.evp_removed,
+ digest.evp_bounce - last.evp_bounce);
+
+ last = digest;
+ count++;
+ sleep(1);
+ }
+
+ return (0);
+}
+
+static int
+do_pause_envelope(int argc, struct parameter *argv)
+{
+ size_t total, ok;
+
+ srv_foreach_envelope(argv, IMSG_CTL_PAUSE_EVP, &total, &ok);
+ printf("%zu envelope%s paused\n", ok, (ok > 1) ? "s" : "");
+
+ return (0);
+}
+
+static int
+do_pause_mda(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_PAUSE_MDA, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_pause_mta(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_PAUSE_MTA, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_pause_smtp(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_PAUSE_SMTP, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_profile(int argc, struct parameter *argv)
+{
+ int v;
+
+ v = str_to_profile(argv[0].u.u_str);
+
+ srv_send(IMSG_CTL_PROFILE_ENABLE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_remove(int argc, struct parameter *argv)
+{
+ size_t total, ok;
+
+ srv_foreach_envelope(argv, IMSG_CTL_REMOVE, &total, &ok);
+ printf("%zu envelope%s removed\n", ok, (ok > 1) ? "s" : "");
+
+ return (0);
+}
+
+static int
+do_resume_envelope(int argc, struct parameter *argv)
+{
+ size_t total, ok;
+
+ srv_foreach_envelope(argv, IMSG_CTL_RESUME_EVP, &total, &ok);
+ printf("%zu envelope%s resumed\n", ok, (ok > 1) ? "s" : "");
+
+ return (0);
+}
+
+static int
+do_resume_mda(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_RESUME_MDA, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_resume_mta(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_RESUME_MTA, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_resume_route(int argc, struct parameter *argv)
+{
+ uint64_t v;
+
+ if (argc == 0)
+ v = 0;
+ else
+ v = argv[0].u.u_routeid;
+
+ srv_send(IMSG_CTL_RESUME_ROUTE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_resume_smtp(int argc, struct parameter *argv)
+{
+ srv_send(IMSG_CTL_RESUME_SMTP, NULL, 0);
+ return srv_check_result(1);
+}
+
+static int
+do_schedule(int argc, struct parameter *argv)
+{
+ size_t total, ok;
+
+ srv_foreach_envelope(argv, IMSG_CTL_SCHEDULE, &total, &ok);
+ printf("%zu envelope%s scheduled\n", ok, (ok > 1) ? "s" : "");
+
+ return (0);
+}
+
+static int
+do_show_envelope(int argc, struct parameter *argv)
+{
+ char buf[PATH_MAX];
+
+ if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/%016" PRIx64,
+ PATH_SPOOL,
+ PATH_QUEUE,
+ (evpid_to_msgid(argv[0].u.u_evpid) & 0xff000000) >> 24,
+ evpid_to_msgid(argv[0].u.u_evpid),
+ argv[0].u.u_evpid))
+ errx(1, "unable to retrieve envelope");
+
+ display(buf);
+
+ return (0);
+}
+
+static int
+do_show_hoststats(int argc, struct parameter *argv)
+{
+ srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTSTATS, NULL, 0);
+
+ return (0);
+}
+
+static int
+do_show_message(int argc, struct parameter *argv)
+{
+ char buf[PATH_MAX];
+ uint32_t msgid;
+
+ if (argv[0].type == P_EVPID)
+ msgid = evpid_to_msgid(argv[0].u.u_evpid);
+ else
+ msgid = argv[0].u.u_msgid;
+
+ if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/message",
+ PATH_SPOOL,
+ PATH_QUEUE,
+ (msgid & 0xff000000) >> 24,
+ msgid))
+ errx(1, "unable to retrieve message");
+
+ display(buf);
+
+ return (0);
+}
+
+static int
+do_show_queue(int argc, struct parameter *argv)
+{
+ struct envelope evp;
+ uint32_t msgid;
+ FTS *fts;
+ FTSENT *ftse;
+ char *qpath[] = {"/queue", NULL};
+ char *tmp;
+ uint64_t evpid;
+
+ now = time(NULL);
+
+ if (!srv_connect()) {
+ log_init(1, LOG_MAIL);
+ queue_init("fs", 0);
+ if (chroot(PATH_SPOOL) == -1 || chdir("/") == -1)
+ err(1, "%s", PATH_SPOOL);
+ fts = fts_open(qpath, FTS_PHYSICAL|FTS_NOCHDIR, NULL);
+ if (fts == NULL)
+ err(1, "%s/queue", PATH_SPOOL);
+
+ while ((ftse = fts_read(fts)) != NULL) {
+ switch (ftse->fts_info) {
+ case FTS_DP:
+ case FTS_DNR:
+ break;
+ case FTS_F:
+ tmp = NULL;
+ evpid = strtoull(ftse->fts_name, &tmp, 16);
+ if (tmp && *tmp != '\0')
+ break;
+ show_offline_envelope(evpid);
+ }
+ }
+
+ fts_close(fts);
+ return (0);
+ }
+
+ if (argc == 0) {
+ msgid = 0;
+ while (srv_iter_messages(&msgid))
+ while (srv_iter_envelopes(msgid, &evp))
+ show_queue_envelope(&evp, 1);
+ } else if (argv[0].type == P_MSGID) {
+ while (srv_iter_envelopes(argv[0].u.u_msgid, &evp))
+ show_queue_envelope(&evp, 1);
+ }
+
+ return (0);
+}
+
+static int
+do_show_hosts(int argc, struct parameter *argv)
+{
+ srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTS, NULL, 0);
+
+ return (0);
+}
+
+static int
+do_show_relays(int argc, struct parameter *argv)
+{
+ srv_show_cmd(IMSG_CTL_MTA_SHOW_RELAYS, NULL, 0);
+
+ return (0);
+}
+
+static int
+do_show_routes(int argc, struct parameter *argv)
+{
+ srv_show_cmd(IMSG_CTL_MTA_SHOW_ROUTES, NULL, 0);
+
+ return (0);
+}
+
+static int
+do_show_stats(int argc, struct parameter *argv)
+{
+ struct stat_kv kv;
+ time_t duration;
+
+ memset(&kv, 0, sizeof kv);
+
+ while (1) {
+ srv_send(IMSG_CTL_GET_STATS, &kv, sizeof kv);
+ srv_recv(IMSG_CTL_GET_STATS);
+ srv_read(&kv, sizeof(kv));
+ srv_end();
+
+ if (kv.iter == NULL)
+ break;
+
+ if (strcmp(kv.key, "uptime") == 0) {
+ duration = time(NULL) - kv.val.u.counter;
+ printf("uptime=%lld\n", (long long)duration);
+ printf("uptime.human=%s\n",
+ duration_to_text(duration));
+ }
+ else {
+ switch (kv.val.type) {
+ case STAT_COUNTER:
+ printf("%s=%zd\n",
+ kv.key, kv.val.u.counter);
+ break;
+ case STAT_TIMESTAMP:
+ printf("%s=%" PRId64 "\n",
+ kv.key, (int64_t)kv.val.u.timestamp);
+ break;
+ case STAT_TIMEVAL:
+ printf("%s=%lld.%lld\n",
+ kv.key, (long long)kv.val.u.tv.tv_sec,
+ (long long)kv.val.u.tv.tv_usec);
+ break;
+ case STAT_TIMESPEC:
+ printf("%s=%lld.%06ld\n",
+ kv.key,
+ (long long)kv.val.u.ts.tv_sec * 1000000 +
+ kv.val.u.ts.tv_nsec / 1000000,
+ kv.val.u.ts.tv_nsec % 1000000);
+ break;
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+do_show_status(int argc, struct parameter *argv)
+{
+ uint32_t sc_flags;
+
+ srv_send(IMSG_CTL_SHOW_STATUS, NULL, 0);
+ srv_recv(IMSG_CTL_SHOW_STATUS);
+ srv_read(&sc_flags, sizeof(sc_flags));
+ srv_end();
+ printf("MDA %s\n",
+ (sc_flags & SMTPD_MDA_PAUSED) ? "paused" : "running");
+ printf("MTA %s\n",
+ (sc_flags & SMTPD_MTA_PAUSED) ? "paused" : "running");
+ printf("SMTP %s\n",
+ (sc_flags & SMTPD_SMTP_PAUSED) ? "paused" : "running");
+ return (0);
+}
+
+static int
+do_trace(int argc, struct parameter *argv)
+{
+ int v;
+
+ v = str_to_trace(argv[0].u.u_str);
+
+ srv_send(IMSG_CTL_TRACE_ENABLE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_unprofile(int argc, struct parameter *argv)
+{
+ int v;
+
+ v = str_to_profile(argv[0].u.u_str);
+
+ srv_send(IMSG_CTL_PROFILE_DISABLE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_untrace(int argc, struct parameter *argv)
+{
+ int v;
+
+ v = str_to_trace(argv[0].u.u_str);
+
+ srv_send(IMSG_CTL_TRACE_DISABLE, &v, sizeof(v));
+ return srv_check_result(1);
+}
+
+static int
+do_update_table(int argc, struct parameter *argv)
+{
+ const char *name = argv[0].u.u_str;
+
+ srv_send(IMSG_CTL_UPDATE_TABLE, name, strlen(name) + 1);
+ return srv_check_result(1);
+}
+
+static int
+do_encrypt(int argc, struct parameter *argv)
+{
+ const char *p = NULL;
+
+ droppriv();
+
+ if (argv)
+ p = argv[0].u.u_str;
+ execl(PATH_ENCRYPT, "encrypt", "--", p, (char *)NULL);
+ errx(1, "execl");
+}
+
+static int
+do_block_mta(int argc, struct parameter *argv)
+{
+ struct ibuf *m;
+
+ if (ibuf == NULL && !srv_connect())
+ errx(1, "smtpd doesn't seem to be running");
+ m = imsg_create(ibuf, IMSG_CTL_MTA_BLOCK, IMSG_VERSION, 0,
+ sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1);
+ if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1)
+ errx(1, "imsg_add");
+ if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1)
+ errx(1, "imsg_add");
+ imsg_close(ibuf, m);
+
+ return srv_check_result(1);
+}
+
+static int
+do_unblock_mta(int argc, struct parameter *argv)
+{
+ struct ibuf *m;
+
+ if (ibuf == NULL && !srv_connect())
+ errx(1, "smtpd doesn't seem to be running");
+
+ m = imsg_create(ibuf, IMSG_CTL_MTA_UNBLOCK, IMSG_VERSION, 0,
+ sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1);
+ if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1)
+ errx(1, "imsg_add");
+ if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1)
+ errx(1, "imsg_add");
+ imsg_close(ibuf, m);
+
+ return srv_check_result(1);
+}
+
+static int
+do_show_mta_block(int argc, struct parameter *argv)
+{
+ srv_show_cmd(IMSG_CTL_MTA_SHOW_BLOCK, NULL, 0);
+
+ return (0);
+}
+
+static int
+do_discover(int argc, struct parameter *argv)
+{
+ uint64_t evpid;
+ uint32_t msgid;
+ size_t n_evp;
+
+ if (ibuf == NULL && !srv_connect())
+ errx(1, "smtpd doesn't seem to be running");
+
+ if (argv[0].type == P_EVPID) {
+ evpid = argv[0].u.u_evpid;
+ srv_send(IMSG_CTL_DISCOVER_EVPID, &evpid, sizeof evpid);
+ srv_recv(IMSG_CTL_DISCOVER_EVPID);
+ } else {
+ msgid = argv[0].u.u_msgid;
+ srv_send(IMSG_CTL_DISCOVER_MSGID, &msgid, sizeof msgid);
+ srv_recv(IMSG_CTL_DISCOVER_MSGID);
+ }
+
+ if (rlen == 0) {
+ srv_end();
+ return (0);
+ } else {
+ srv_read(&n_evp, sizeof n_evp);
+ srv_end();
+ }
+
+ printf("%zu envelope%s discovered\n", n_evp, (n_evp != 1) ? "s" : "");
+ return (0);
+}
+
+static int
+do_spf_walk(int argc, struct parameter *argv)
+{
+ droppriv();
+
+ return spfwalk(argc, argv);
+}
+
+#define cmd_install_priv(s, f) \
+ cmd_install((s), privileged ? (f) : do_permission_denied)
+
+int
+main(int argc, char **argv)
+{
+ gid_t gid;
+ struct group *gr;
+ int privileged;
+ char *argv_mailq[] = { "show", "queue", NULL };
+
+#ifndef HAVE___PROGNAME
+ __progname = ssh_get_progname(argv[0]);
+#endif
+
+ /* check that smtpctl was installed setgid */
+ if ((gr = getgrnam(SMTPD_QUEUE_GROUP)) == NULL)
+ errx(1, "unknown group %s", SMTPD_QUEUE_GROUP);
+ else if (gr->gr_gid != getegid())
+ errx(1, "this program must be setgid %s", SMTPD_QUEUE_GROUP);
+
+ sendmail_compat(argc, argv);
+ privileged = geteuid() == 0;
+
+ gid = getgid();
+ if (setresgid(gid, gid, gid) == -1)
+ err(1, "setresgid");
+
+ /* Privileged commands */
+ cmd_install_priv("discover <evpid>", do_discover);
+ cmd_install_priv("discover <msgid>", do_discover);
+ cmd_install_priv("pause mta from <addr> for <str>", do_block_mta);
+ cmd_install_priv("resume mta from <addr> for <str>", do_unblock_mta);
+ cmd_install_priv("show mta paused", do_show_mta_block);
+ cmd_install_priv("log brief", do_log_brief);
+ cmd_install_priv("log verbose", do_log_verbose);
+ cmd_install_priv("monitor", do_monitor);
+ cmd_install_priv("pause envelope <evpid>", do_pause_envelope);
+ cmd_install_priv("pause envelope <msgid>", do_pause_envelope);
+ cmd_install_priv("pause envelope all", do_pause_envelope);
+ cmd_install_priv("pause mda", do_pause_mda);
+ cmd_install_priv("pause mta", do_pause_mta);
+ cmd_install_priv("pause smtp", do_pause_smtp);
+ cmd_install_priv("profile <str>", do_profile);
+ cmd_install_priv("remove <evpid>", do_remove);
+ cmd_install_priv("remove <msgid>", do_remove);
+ cmd_install_priv("remove all", do_remove);
+ cmd_install_priv("resume envelope <evpid>", do_resume_envelope);
+ cmd_install_priv("resume envelope <msgid>", do_resume_envelope);
+ cmd_install_priv("resume envelope all", do_resume_envelope);
+ cmd_install_priv("resume mda", do_resume_mda);
+ cmd_install_priv("resume mta", do_resume_mta);
+ cmd_install_priv("resume route <routeid>", do_resume_route);
+ cmd_install_priv("resume smtp", do_resume_smtp);
+ cmd_install_priv("schedule <msgid>", do_schedule);
+ cmd_install_priv("schedule <evpid>", do_schedule);
+ cmd_install_priv("schedule all", do_schedule);
+ cmd_install_priv("show envelope <evpid>", do_show_envelope);
+ cmd_install_priv("show hoststats", do_show_hoststats);
+ cmd_install_priv("show message <msgid>", do_show_message);
+ cmd_install_priv("show message <evpid>", do_show_message);
+ cmd_install_priv("show queue", do_show_queue);
+ cmd_install_priv("show queue <msgid>", do_show_queue);
+ cmd_install_priv("show hosts", do_show_hosts);
+ cmd_install_priv("show relays", do_show_relays);
+ cmd_install_priv("show routes", do_show_routes);
+ cmd_install_priv("show stats", do_show_stats);
+ cmd_install_priv("show status", do_show_status);
+ cmd_install_priv("trace <str>", do_trace);
+ cmd_install_priv("unprofile <str>", do_unprofile);
+ cmd_install_priv("untrace <str>", do_untrace);
+ cmd_install_priv("update table <str>", do_update_table);
+
+ /* Unprivileged commands */
+ cmd_install("encrypt", do_encrypt);
+ cmd_install("encrypt <str>", do_encrypt);
+ cmd_install("spf walk", do_spf_walk);
+
+ if (strcmp(__progname, "mailq") == 0)
+ return cmd_run(2, argv_mailq);
+ if (strcmp(__progname, "smtpctl") == 0)
+ return cmd_run(argc - 1, argv + 1);
+
+ errx(1, "unsupported mode");
+ return (0);
+}
+
+void
+sendmail_compat(int argc, char **argv)
+{
+ FILE *offlinefp = NULL;
+ gid_t gid;
+ int i, r;
+
+ if (strcmp(__progname, "sendmail") == 0 ||
+ strcmp(__progname, "send-mail") == 0) {
+ /*
+ * determine whether we are called with flags
+ * that should invoke makemap/newaliases.
+ */
+ for (i = 1; i < argc; i++)
+ if (strncmp(argv[i], "-bi", 3) == 0)
+ exit(makemap(P_SENDMAIL, argc, argv));
+
+ if (!srv_connect())
+ offlinefp = offline_file();
+
+ gid = getgid();
+ if (setresgid(gid, gid, gid) == -1)
+ err(1, "setresgid");
+
+#if HAVE_PLEDGE
+ /* we'll reduce further down the road */
+ if (pledge("stdio rpath wpath cpath tmppath flock "
+ "dns getpw recvfd", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ sendmail = 1;
+ exit(enqueue(argc, argv, offlinefp));
+ } else if (strcmp(__progname, "makemap") == 0)
+ exit(makemap(P_MAKEMAP, argc, argv));
+ else if (strcmp(__progname, "newaliases") == 0) {
+ r = makemap(P_NEWALIASES, argc, argv);
+ /*
+ * if server is available, notify of table update.
+ * only makes sense for static tables AND if server is up.
+ */
+ if (srv_connect()) {
+ srv_send(IMSG_CTL_UPDATE_TABLE, "aliases", strlen("aliases") + 1);
+ srv_check_result(0);
+ }
+ exit(r);
+ }
+}
+
+static void
+show_queue_envelope(struct envelope *e, int online)
+{
+ const char *src = "?", *agent = "?";
+ char status[128], runstate[128], errline[LINE_MAX];
+
+ status[0] = '\0';
+
+ getflag(&e->flags, EF_BOUNCE, "bounce", status, sizeof(status));
+ getflag(&e->flags, EF_AUTHENTICATED, "auth", status, sizeof(status));
+ getflag(&e->flags, EF_INTERNAL, "internal", status, sizeof(status));
+ getflag(&e->flags, EF_SUSPEND, "suspend", status, sizeof(status));
+ getflag(&e->flags, EF_HOLD, "hold", status, sizeof(status));
+
+ if (online) {
+ if (e->flags & EF_PENDING)
+ (void)snprintf(runstate, sizeof runstate, "pending|%zd",
+ (ssize_t)(e->nexttry - now));
+ else if (e->flags & EF_INFLIGHT)
+ (void)snprintf(runstate, sizeof runstate,
+ "inflight|%zd", (ssize_t)(now - e->lasttry));
+ else
+ (void)snprintf(runstate, sizeof runstate, "invalid|");
+ e->flags &= ~(EF_PENDING|EF_INFLIGHT);
+ }
+ else
+ (void)strlcpy(runstate, "offline|", sizeof runstate);
+
+ if (e->flags)
+ errx(1, "%016" PRIx64 ": unexpected flags 0x%04x", e->id,
+ e->flags);
+
+ if (status[0])
+ status[strlen(status) - 1] = '\0';
+
+ if (e->type == D_MDA)
+ agent = "mda";
+ else if (e->type == D_MTA)
+ agent = "mta";
+ else if (e->type == D_BOUNCE)
+ agent = "bounce";
+
+ if (e->ss.ss_family == AF_LOCAL)
+ src = "local";
+ else if (e->ss.ss_family == AF_INET)
+ src = "inet4";
+ else if (e->ss.ss_family == AF_INET6)
+ src = "inet6";
+
+ strnvis(errline, e->errorline, sizeof(errline), 0);
+
+ printf("%016"PRIx64
+ "|%s|%s|%s|%s@%s|%s@%s|%s@%s"
+ "|%zu|%zu|%zu|%zu|%s|%s\n",
+
+ e->id,
+
+ src,
+ agent,
+ status,
+ e->sender.user, e->sender.domain,
+ e->rcpt.user, e->rcpt.domain,
+ e->dest.user, e->dest.domain,
+
+ (size_t) e->creation,
+ (size_t) (e->creation + e->ttl),
+ (size_t) e->lasttry,
+ (size_t) e->retry,
+ runstate,
+ errline);
+}
+
+static void
+getflag(uint *bitmap, int bit, char *bitstr, char *buf, size_t len)
+{
+ if (*bitmap & bit) {
+ *bitmap &= ~bit;
+ (void)strlcat(buf, bitstr, len);
+ (void)strlcat(buf, ",", len);
+ }
+}
+
+static void
+show_offline_envelope(uint64_t evpid)
+{
+ FILE *fp = NULL;
+ char pathname[PATH_MAX];
+ size_t plen;
+ char *p;
+ size_t buflen;
+ char buffer[sizeof(struct envelope)];
+
+ struct envelope evp;
+
+ if (!bsnprintf(pathname, sizeof pathname,
+ "/queue/%02x/%08x/%016"PRIx64,
+ (evpid_to_msgid(evpid) & 0xff000000) >> 24,
+ evpid_to_msgid(evpid), evpid))
+ goto end;
+ fp = fopen(pathname, "r");
+ if (fp == NULL)
+ goto end;
+
+ buflen = fread(buffer, 1, sizeof (buffer) - 1, fp);
+ p = buffer;
+ plen = buflen;
+ buffer[buflen] = '\0';
+
+ if (is_encrypted_buffer(p)) {
+ warnx("offline encrypted queue is not supported yet");
+ goto end;
+ }
+
+ if (is_gzip_buffer(p)) {
+ warnx("offline compressed queue is not supported yet");
+ goto end;
+ }
+
+ if (!envelope_load_buffer(&evp, p, plen))
+ goto end;
+ evp.id = evpid;
+ show_queue_envelope(&evp, 0);
+
+end:
+ if (fp)
+ fclose(fp);
+}
+
+static void
+display(const char *s)
+{
+ FILE *fp;
+ char *key;
+ int gzipped;
+ char *gzcat_argv0 = strrchr(PATH_GZCAT, '/') + 1;
+
+ if ((fp = fopen(s, "r")) == NULL)
+ err(1, "fopen");
+
+ if (is_encrypted_fp(fp)) {
+ int i;
+ FILE *ofp = NULL;
+
+ if ((ofp = tmpfile()) == NULL)
+ err(1, "tmpfile");
+
+ for (i = 0; i < 3; i++) {
+ key = getpass("key> ");
+ if (crypto_setup(key, strlen(key)))
+ break;
+ }
+ if (i == 3)
+ errx(1, "crypto-setup: invalid key");
+
+ if (!crypto_decrypt_file(fp, ofp)) {
+ printf("object is encrypted: %s\n", key);
+ exit(1);
+ }
+
+ fclose(fp);
+ fp = ofp;
+ fseek(fp, 0, SEEK_SET);
+ }
+ gzipped = is_gzip_fp(fp);
+
+ lseek(fileno(fp), 0, SEEK_SET);
+ (void)dup2(fileno(fp), STDIN_FILENO);
+ if (gzipped)
+ execl(PATH_GZCAT, gzcat_argv0, (char *)NULL);
+ else
+ execl(PATH_CAT, "cat", (char *)NULL);
+ err(1, "execl");
+}
+
+static int
+str_to_trace(const char *str)
+{
+ if (!strcmp(str, "imsg"))
+ return TRACE_IMSG;
+ if (!strcmp(str, "io"))
+ return TRACE_IO;
+ if (!strcmp(str, "smtp"))
+ return TRACE_SMTP;
+ if (!strcmp(str, "filters"))
+ return TRACE_FILTERS;
+ if (!strcmp(str, "mta"))
+ return TRACE_MTA;
+ if (!strcmp(str, "bounce"))
+ return TRACE_BOUNCE;
+ if (!strcmp(str, "scheduler"))
+ return TRACE_SCHEDULER;
+ if (!strcmp(str, "lookup"))
+ return TRACE_LOOKUP;
+ if (!strcmp(str, "stat"))
+ return TRACE_STAT;
+ if (!strcmp(str, "rules"))
+ return TRACE_RULES;
+ if (!strcmp(str, "mproc"))
+ return TRACE_MPROC;
+ if (!strcmp(str, "expand"))
+ return TRACE_EXPAND;
+ if (!strcmp(str, "all"))
+ return ~TRACE_DEBUG;
+ errx(1, "invalid trace keyword: %s", str);
+ return (0);
+}
+
+static int
+str_to_profile(const char *str)
+{
+ if (!strcmp(str, "imsg"))
+ return PROFILE_IMSG;
+ if (!strcmp(str, "queue"))
+ return PROFILE_QUEUE;
+ errx(1, "invalid profile keyword: %s", str);
+ return (0);
+}
+
+static int
+is_gzip_buffer(const char *buffer)
+{
+ uint16_t magic;
+
+ memcpy(&magic, buffer, sizeof magic);
+#define GZIP_MAGIC 0x8b1f
+ return (magic == GZIP_MAGIC);
+}
+
+static int
+is_gzip_fp(FILE *fp)
+{
+ uint8_t magic[2];
+ int ret = 0;
+
+ if (fread(&magic, 1, sizeof magic, fp) != sizeof magic)
+ goto end;
+
+ ret = is_gzip_buffer((const char *)&magic);
+end:
+ fseek(fp, 0, SEEK_SET);
+ return ret;
+}
+
+
+/* XXX */
+/*
+ * queue supports transparent encryption.
+ * encrypted chunks are prefixed with an API version byte
+ * which we ensure is unambiguous with gzipped / plain
+ * objects.
+ */
+
+static int
+is_encrypted_buffer(const char *buffer)
+{
+ uint8_t magic;
+
+ magic = *buffer;
+#define ENCRYPTION_MAGIC 0x1
+ return (magic == ENCRYPTION_MAGIC);
+}
+
+static int
+is_encrypted_fp(FILE *fp)
+{
+ uint8_t magic;
+ int ret = 0;
+
+ if (fread(&magic, 1, sizeof magic, fp) != sizeof magic)
+ goto end;
+
+ ret = is_encrypted_buffer((const char *)&magic);
+end:
+ fseek(fp, 0, SEEK_SET);
+ return ret;
+}