aboutsummaryrefslogtreecommitdiffstats
path: root/smtpd/enqueue.c
diff options
context:
space:
mode:
Diffstat (limited to 'smtpd/enqueue.c')
-rw-r--r--smtpd/enqueue.c932
1 files changed, 932 insertions, 0 deletions
diff --git a/smtpd/enqueue.c b/smtpd/enqueue.c
new file mode 100644
index 00000000..0ef694b5
--- /dev/null
+++ b/smtpd/enqueue.c
@@ -0,0 +1,932 @@
+/* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric Exp $ */
+
+/*
+ * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
+ * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
+ * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <grp.h>
+#include <imsg.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "smtpd.h"
+
+extern struct imsgbuf *ibuf;
+
+void usage(void);
+static void build_from(char *, struct passwd *);
+static int parse_message(FILE *, int, int, FILE *);
+static void parse_addr(char *, size_t, int);
+static void parse_addr_terminal(int);
+static char *qualify_addr(char *);
+static void rcpt_add(char *);
+static int open_connection(void);
+static int get_responses(FILE *, int);
+static int send_line(FILE *, int, char *, ...);
+static int enqueue_offline(int, char *[], FILE *, FILE *);
+static int savedeadletter(struct passwd *, FILE *);
+
+extern int srv_connected(void);
+
+enum headerfields {
+ HDR_NONE,
+ HDR_FROM,
+ HDR_TO,
+ HDR_CC,
+ HDR_BCC,
+ HDR_SUBJECT,
+ HDR_DATE,
+ HDR_MSGID,
+ HDR_MIME_VERSION,
+ HDR_CONTENT_TYPE,
+ HDR_CONTENT_DISPOSITION,
+ HDR_CONTENT_TRANSFER_ENCODING,
+ HDR_USER_AGENT
+};
+
+struct {
+ char *word;
+ enum headerfields type;
+} keywords[] = {
+ { "From:", HDR_FROM },
+ { "To:", HDR_TO },
+ { "Cc:", HDR_CC },
+ { "Bcc:", HDR_BCC },
+ { "Subject:", HDR_SUBJECT },
+ { "Date:", HDR_DATE },
+ { "Message-Id:", HDR_MSGID },
+ { "MIME-Version:", HDR_MIME_VERSION },
+ { "Content-Type:", HDR_CONTENT_TYPE },
+ { "Content-Disposition:", HDR_CONTENT_DISPOSITION },
+ { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING },
+ { "User-Agent:", HDR_USER_AGENT },
+};
+
+#define LINESPLIT 990
+#define SMTP_LINELEN 1000
+#define TIMEOUTMSG "Timeout\n"
+
+#define WSP(c) (c == ' ' || c == '\t')
+
+int verbose = 0;
+static char host[HOST_NAME_MAX+1];
+char *user = NULL;
+time_t timestamp;
+
+struct {
+ int fd;
+ char *from;
+ char *fromname;
+ char **rcpts;
+ char *dsn_notify;
+ char *dsn_ret;
+ char *dsn_envid;
+ int rcpt_cnt;
+ int need_linesplit;
+ int saw_date;
+ int saw_msgid;
+ int saw_from;
+ int saw_mime_version;
+ int saw_content_type;
+ int saw_content_disposition;
+ int saw_content_transfer_encoding;
+ int saw_user_agent;
+ int noheader;
+} msg;
+
+struct {
+ uint quote;
+ uint comment;
+ uint esc;
+ uint brackets;
+ size_t wpos;
+ char buf[SMTP_LINELEN];
+} pstate;
+
+#define QP_TEST_WRAP(fp, buf, linelen, size) do { \
+ if (((linelen) += (size)) + 1 > 76) { \
+ fprintf((fp), "=\r\n"); \
+ if (buf[0] == '.') \
+ fprintf((fp), "."); \
+ (linelen) = (size); \
+ } \
+} while (0)
+
+/* RFC 2045 section 6.7 */
+static void
+qp_encoded_write(FILE *fp, char *buf)
+{
+ size_t linelen = 0;
+
+ for (;buf[0] != '\0' && buf[0] != '\n'; buf++) {
+ /*
+ * Point 3: Any TAB (HT) or SPACE characters on an encoded line
+ * MUST thus be followed on that line by a printable character.
+ *
+ * Ergo, only encode if the next character is EOL.
+ */
+ if (buf[0] == ' ' || buf[0] == '\t') {
+ if (buf[1] == '\n') {
+ QP_TEST_WRAP(fp, buf, linelen, 3);
+ fprintf(fp, "=%2X", *buf & 0xff);
+ } else {
+ QP_TEST_WRAP(fp, buf, linelen, 1);
+ fprintf(fp, "%c", *buf & 0xff);
+ }
+ /*
+ * Point 1, with exclusion of point 2, skip EBCDIC NOTE.
+ * Do this after whitespace check, else they would match here.
+ */
+ } else if (!((buf[0] >= 33 && buf[0] <= 60) ||
+ (buf[0] >= 62 && buf[0] <= 126))) {
+ QP_TEST_WRAP(fp, buf, linelen, 3);
+ fprintf(fp, "=%2X", *buf & 0xff);
+ /* Point 2: 33 through 60 inclusive, and 62 through 126 */
+ } else {
+ QP_TEST_WRAP(fp, buf, linelen, 1);
+ fprintf(fp, "%c", *buf);
+ }
+ }
+ fprintf(fp, "\r\n");
+}
+
+int
+enqueue(int argc, char *argv[], FILE *ofp)
+{
+ int i, ch, tflag = 0;
+ char *fake_from = NULL, *buf = NULL;
+ struct passwd *pw;
+ FILE *fp = NULL, *fout;
+ size_t sz = 0, envid_sz = 0;
+ ssize_t len;
+ char *line;
+ int inheaders = 1;
+ int save_argc;
+ char **save_argv;
+ int no_getlogin = 0;
+
+ memset(&msg, 0, sizeof(msg));
+ time(&timestamp);
+
+ save_argc = argc;
+ save_argv = argv;
+
+ while ((ch = getopt(argc, argv,
+ "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) {
+ switch (ch) {
+ case 'f':
+ fake_from = optarg;
+ break;
+ case 'F':
+ msg.fromname = optarg;
+ break;
+ case 'N':
+ msg.dsn_notify = optarg;
+ break;
+ case 'r':
+ fake_from = optarg;
+ break;
+ case 'R':
+ msg.dsn_ret = optarg;
+ break;
+ case 'S':
+ no_getlogin = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'V':
+ msg.dsn_envid = optarg;
+ break;
+ /* all remaining: ignored, sendmail compat */
+ case 'A':
+ case 'B':
+ case 'b':
+ case 'E':
+ case 'e':
+ case 'i':
+ case 'L':
+ case 'm':
+ case 'o':
+ case 'p':
+ case 'x':
+ break;
+ case 'q':
+ /* XXX: implement "process all now" */
+ return (EX_SOFTWARE);
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (getmailname(host, sizeof(host)) == -1)
+ errx(EX_NOHOST, "getmailname");
+ if (no_getlogin) {
+ if ((pw = getpwuid(getuid())) == NULL)
+ user = "anonymous";
+ if (pw != NULL)
+ user = xstrdup(pw->pw_name);
+ }
+ else {
+ uid_t ruid = getuid();
+
+ if ((user = getlogin()) != NULL && *user != '\0') {
+ if ((pw = getpwnam(user)) == NULL ||
+ (ruid != 0 && ruid != pw->pw_uid))
+ pw = getpwuid(ruid);
+ } else if ((pw = getpwuid(ruid)) == NULL) {
+ user = "anonymous";
+ }
+ user = xstrdup(pw ? pw->pw_name : user);
+ }
+
+ build_from(fake_from, pw);
+
+ while (argc > 0) {
+ rcpt_add(argv[0]);
+ argv++;
+ argc--;
+ }
+
+ if ((fp = tmpfile()) == NULL)
+ err(EX_UNAVAILABLE, "tmpfile");
+
+ msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
+
+ if (msg.rcpt_cnt == 0)
+ errx(EX_SOFTWARE, "no recipients");
+
+ /* init session */
+ rewind(fp);
+
+ /* check if working in offline mode */
+ /* If the server is not running, enqueue the message offline */
+
+ if (!srv_connected()) {
+#if HAVE_PLEDGE
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+#endif
+ return (enqueue_offline(save_argc, save_argv, fp, ofp));
+ }
+
+ if ((msg.fd = open_connection()) == -1)
+ errx(EX_UNAVAILABLE, "server too busy");
+
+#if HAVE_PLEDGE
+ if (pledge("stdio wpath cpath", NULL) == -1)
+ err(1, "pledge");
+#endif
+
+ fout = fdopen(msg.fd, "a+");
+ if (fout == NULL)
+ err(EX_UNAVAILABLE, "fdopen");
+
+ /*
+ * We need to call get_responses after every command because we don't
+ * support PIPELINING on the server-side yet.
+ */
+
+ /* banner */
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ if (!send_line(fout, verbose, "EHLO localhost\r\n"))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ if (msg.dsn_envid != NULL)
+ envid_sz = strlen(msg.dsn_envid);
+
+ if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n",
+ msg.from,
+ msg.dsn_ret ? "RET=" : "",
+ msg.dsn_ret ? msg.dsn_ret : "",
+ envid_sz ? "ENVID=" : "",
+ envid_sz ? msg.dsn_envid : ""))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ for (i = 0; i < msg.rcpt_cnt; i++) {
+ if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n",
+ msg.rcpts[i],
+ msg.dsn_notify ? "NOTIFY=" : "",
+ msg.dsn_notify ? msg.dsn_notify : ""))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+ }
+
+ if (!send_line(fout, verbose, "DATA\r\n"))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ /* add From */
+ if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n",
+ msg.fromname ? msg.fromname : "", msg.fromname ? " " : "",
+ msg.from))
+ goto fail;
+
+ /* add Date */
+ if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n",
+ time_to_text(timestamp)))
+ goto fail;
+
+ if (msg.need_linesplit) {
+ /* we will always need to mime encode for long lines */
+ if (!msg.saw_mime_version && !send_line(fout, 0,
+ "MIME-Version: 1.0\r\n"))
+ goto fail;
+ if (!msg.saw_content_type && !send_line(fout, 0,
+ "Content-Type: text/plain; charset=unknown-8bit\r\n"))
+ goto fail;
+ if (!msg.saw_content_disposition && !send_line(fout, 0,
+ "Content-Disposition: inline\r\n"))
+ goto fail;
+ if (!msg.saw_content_transfer_encoding && !send_line(fout, 0,
+ "Content-Transfer-Encoding: quoted-printable\r\n"))
+ goto fail;
+ }
+
+ /* add separating newline */
+ if (msg.noheader) {
+ if (!send_line(fout, 0, "\r\n"))
+ goto fail;
+ inheaders = 0;
+ }
+
+ for (;;) {
+ if ((len = getline(&buf, &sz, fp)) == -1) {
+ if (feof(fp))
+ break;
+ else
+ err(EX_UNAVAILABLE, "getline");
+ }
+
+ /* newlines have been normalized on first parsing */
+ if (buf[len-1] != '\n')
+ errx(EX_SOFTWARE, "expect EOL");
+ len--;
+
+ if (buf[0] == '.') {
+ if (fputc('.', fout) == EOF)
+ goto fail;
+ }
+
+ line = buf;
+
+ if (inheaders) {
+ if (strncasecmp("from ", line, 5) == 0)
+ continue;
+ if (strncasecmp("return-path: ", line, 13) == 0)
+ continue;
+ }
+
+ if (msg.saw_content_transfer_encoding || msg.noheader ||
+ inheaders || !msg.need_linesplit) {
+ if (!send_line(fout, 0, "%.*s\r\n", (int)len, line))
+ goto fail;
+ if (inheaders && buf[0] == '\n')
+ inheaders = 0;
+ continue;
+ }
+
+ /* we don't have a content transfer encoding, use our default */
+ qp_encoded_write(fout, line);
+ }
+ free(buf);
+ if (!send_line(fout, verbose, ".\r\n"))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ if (!send_line(fout, verbose, "QUIT\r\n"))
+ goto fail;
+ if (!get_responses(fout, 1))
+ goto fail;
+
+ fclose(fp);
+ fclose(fout);
+
+ exit(EX_OK);
+
+fail:
+ if (pw)
+ savedeadletter(pw, fp);
+ exit(EX_SOFTWARE);
+}
+
+static int
+get_responses(FILE *fin, int n)
+{
+ char *buf = NULL;
+ size_t sz = 0;
+ ssize_t len;
+ int e, ret = 0;
+
+ fflush(fin);
+ if ((e = ferror(fin))) {
+ warnx("ferror: %d", e);
+ goto err;
+ }
+
+ while (n) {
+ if ((len = getline(&buf, &sz, fin)) == -1) {
+ if (ferror(fin)) {
+ warn("getline");
+ goto err;
+ } else if (feof(fin))
+ break;
+ else
+ err(EX_UNAVAILABLE, "getline");
+ }
+
+ /* account for \r\n linebreaks */
+ if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
+ buf[--len - 1] = '\n';
+
+ if (len < 4) {
+ warnx("bad response");
+ goto err;
+ }
+
+ if (verbose)
+ printf("<<< %.*s", (int)len, buf);
+
+ if (buf[3] == '-')
+ continue;
+ if (buf[0] != '2' && buf[0] != '3') {
+ warnx("command failed: %.*s", (int)len, buf);
+ goto err;
+ }
+ n--;
+ }
+
+ ret = 1;
+err:
+ free(buf);
+ return ret;
+}
+
+static int
+send_line(FILE *fp, int v, char *fmt, ...)
+{
+ int ret = 0;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (vfprintf(fp, fmt, ap) >= 0)
+ ret = 1;
+ va_end(ap);
+
+ if (ret && v) {
+ printf(">>> ");
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ }
+
+ return (ret);
+}
+
+static void
+build_from(char *fake_from, struct passwd *pw)
+{
+ char *p;
+
+ if (fake_from == NULL)
+ msg.from = qualify_addr(user);
+ else {
+ if (fake_from[0] == '<') {
+ if (fake_from[strlen(fake_from) - 1] != '>')
+ errx(1, "leading < but no trailing >");
+ fake_from[strlen(fake_from) - 1] = 0;
+ p = xstrdup(fake_from + 1);
+
+ msg.from = qualify_addr(p);
+ free(p);
+ } else
+ msg.from = qualify_addr(fake_from);
+ }
+
+ if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
+ int len, apos;
+
+ len = strcspn(pw->pw_gecos, ",");
+ if ((p = memchr(pw->pw_gecos, '&', len))) {
+ apos = p - pw->pw_gecos;
+ if (asprintf(&msg.fromname, "%.*s%s%.*s",
+ apos, pw->pw_gecos,
+ pw->pw_name,
+ len - apos - 1, p + 1) == -1)
+ err(1, "asprintf");
+ msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]);
+ } else {
+ if (asprintf(&msg.fromname, "%.*s", len,
+ pw->pw_gecos) == -1)
+ err(1, "asprintf");
+ }
+ }
+}
+
+static int
+parse_message(FILE *fin, int get_from, int tflag, FILE *fout)
+{
+ char *buf = NULL;
+ size_t sz = 0;
+ ssize_t len;
+ uint i, cur = HDR_NONE;
+ uint header_seen = 0, header_done = 0;
+
+ memset(&pstate, 0, sizeof(pstate));
+ for (;;) {
+ if ((len = getline(&buf, &sz, fin)) == -1) {
+ if (feof(fin))
+ break;
+ else
+ err(EX_UNAVAILABLE, "getline");
+ }
+
+ /* account for \r\n linebreaks */
+ if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
+ buf[--len - 1] = '\n';
+
+ if (len == 1 && buf[0] == '\n') /* end of header */
+ header_done = 1;
+
+ if (!WSP(buf[0])) { /* whitespace -> continuation */
+ if (cur == HDR_FROM)
+ parse_addr_terminal(1);
+ if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
+ parse_addr_terminal(0);
+ cur = HDR_NONE;
+ }
+
+ /* not really exact, if we are still in headers */
+ if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT)
+ msg.need_linesplit = 1;
+
+ for (i = 0; !header_done && cur == HDR_NONE &&
+ i < nitems(keywords); i++)
+ if ((size_t)len > strlen(keywords[i].word) &&
+ !strncasecmp(buf, keywords[i].word,
+ strlen(keywords[i].word)))
+ cur = keywords[i].type;
+
+ if (cur != HDR_NONE)
+ header_seen = 1;
+
+ if (cur != HDR_BCC) {
+ if (!send_line(fout, 0, "%.*s", (int)len, buf))
+ err(1, "write error");
+ if (buf[len - 1] != '\n') {
+ if (fputc('\n', fout) == EOF)
+ err(1, "write error");
+ }
+ }
+
+ /*
+ * using From: as envelope sender is not sendmail compatible,
+ * but I really want it that way - maybe needs a knob
+ */
+ if (cur == HDR_FROM) {
+ msg.saw_from++;
+ if (get_from)
+ parse_addr(buf, len, 1);
+ }
+
+ if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
+ parse_addr(buf, len, 0);
+
+ if (cur == HDR_DATE)
+ msg.saw_date++;
+ if (cur == HDR_MSGID)
+ msg.saw_msgid++;
+ if (cur == HDR_MIME_VERSION)
+ msg.saw_mime_version = 1;
+ if (cur == HDR_CONTENT_TYPE)
+ msg.saw_content_type = 1;
+ if (cur == HDR_CONTENT_DISPOSITION)
+ msg.saw_content_disposition = 1;
+ if (cur == HDR_CONTENT_TRANSFER_ENCODING)
+ msg.saw_content_transfer_encoding = 1;
+ if (cur == HDR_USER_AGENT)
+ msg.saw_user_agent = 1;
+ }
+
+ free(buf);
+ return (!header_seen);
+}
+
+static void
+parse_addr(char *s, size_t len, int is_from)
+{
+ size_t pos = 0;
+ int terminal = 0;
+
+ /* unless this is a continuation... */
+ if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
+ /* ... skip over everything before the ':' */
+ for (; pos < len && s[pos] != ':'; pos++)
+ ; /* nothing */
+ /* ... and check & reset parser state */
+ parse_addr_terminal(is_from);
+ }
+
+ /* skip over ':' ',' ';' and whitespace */
+ for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
+ s[pos] == ',' || s[pos] == ';'); pos++)
+ ; /* nothing */
+
+ for (; pos < len; pos++) {
+ if (!pstate.esc && !pstate.quote && s[pos] == '(')
+ pstate.comment++;
+ if (!pstate.comment && !pstate.esc && s[pos] == '"')
+ pstate.quote = !pstate.quote;
+
+ if (!pstate.comment && !pstate.quote && !pstate.esc) {
+ if (s[pos] == ':') { /* group */
+ for (pos++; pos < len && WSP(s[pos]); pos++)
+ ; /* nothing */
+ pstate.wpos = 0;
+ }
+ if (s[pos] == '\n' || s[pos] == '\r')
+ break;
+ if (s[pos] == ',' || s[pos] == ';') {
+ terminal = 1;
+ break;
+ }
+ if (s[pos] == '<') {
+ pstate.brackets = 1;
+ pstate.wpos = 0;
+ }
+ if (pstate.brackets && s[pos] == '>')
+ terminal = 1;
+ }
+
+ if (!pstate.comment && !terminal && (!(!(pstate.quote ||
+ pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
+ if (pstate.wpos >= sizeof(pstate.buf))
+ errx(1, "address exceeds buffer size");
+ pstate.buf[pstate.wpos++] = s[pos];
+ }
+
+ if (!pstate.quote && pstate.comment && s[pos] == ')')
+ pstate.comment--;
+
+ if (!pstate.esc && !pstate.comment && s[pos] == '\\')
+ pstate.esc = 1;
+ else
+ pstate.esc = 0;
+ }
+
+ if (terminal)
+ parse_addr_terminal(is_from);
+
+ for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
+ ; /* nothing */
+
+ if (pos < len)
+ parse_addr(s + pos, len - pos, is_from);
+}
+
+static void
+parse_addr_terminal(int is_from)
+{
+ if (pstate.comment || pstate.quote || pstate.esc)
+ errx(1, "syntax error in address");
+ if (pstate.wpos) {
+ if (pstate.wpos >= sizeof(pstate.buf))
+ errx(1, "address exceeds buffer size");
+ pstate.buf[pstate.wpos] = '\0';
+ if (is_from)
+ msg.from = qualify_addr(pstate.buf);
+ else
+ rcpt_add(pstate.buf);
+ pstate.wpos = 0;
+ }
+}
+
+static char *
+qualify_addr(char *in)
+{
+ char *out;
+
+ if (strlen(in) > 0 && strchr(in, '@') == NULL) {
+ if (asprintf(&out, "%s@%s", in, host) == -1)
+ err(1, "qualify asprintf");
+ } else
+ out = xstrdup(in);
+
+ return (out);
+}
+
+static void
+rcpt_add(char *addr)
+{
+ void *nrcpts;
+ char *p;
+ int n;
+
+ n = 1;
+ p = addr;
+ while ((p = strchr(p, ',')) != NULL) {
+ n++;
+ p++;
+ }
+
+ if ((nrcpts = reallocarray(msg.rcpts,
+ msg.rcpt_cnt + n, sizeof(char *))) == NULL)
+ err(1, "rcpt_add realloc");
+ msg.rcpts = nrcpts;
+
+ while (n--) {
+ if ((p = strchr(addr, ',')) != NULL)
+ *p++ = '\0';
+ msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
+ if (p == NULL)
+ break;
+ addr = p;
+ }
+}
+
+static int
+open_connection(void)
+{
+ struct imsg imsg;
+ int fd;
+ int n;
+
+ imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0);
+
+ while (ibuf->w.queued)
+ if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+ err(1, "write error");
+
+ while (1) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ errx(1, "imsg_read error");
+ if (n == 0)
+ errx(1, "pipe closed");
+
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ errx(1, "imsg_get error");
+ if (n == 0)
+ continue;
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_OK:
+ break;
+ case IMSG_CTL_FAIL:
+ errx(1, "server disallowed submission request");
+ default:
+ errx(1, "unexpected imsg reply type");
+ }
+
+ fd = imsg.fd;
+ imsg_free(&imsg);
+
+ break;
+ }
+
+ return fd;
+}
+
+static int
+enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
+{
+ int i, ch;
+
+ for (i = 1; i < argc; i++) {
+ if (strchr(argv[i], '|') != NULL) {
+ warnx("%s contains illegal character", argv[i]);
+ ftruncate(fileno(ofile), 0);
+ exit(EX_SOFTWARE);
+ }
+ if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
+ goto write_error;
+ }
+
+ if (fputc('\n', ofile) == EOF)
+ goto write_error;
+
+ while ((ch = fgetc(ifile)) != EOF) {
+ if (fputc(ch, ofile) == EOF)
+ goto write_error;
+ }
+
+ if (ferror(ifile)) {
+ warn("read error");
+ ftruncate(fileno(ofile), 0);
+ exit(EX_UNAVAILABLE);
+ }
+
+ if (fclose(ofile) == EOF)
+ goto write_error;
+
+ return (EX_TEMPFAIL);
+write_error:
+ warn("write error");
+ ftruncate(fileno(ofile), 0);
+ exit(EX_UNAVAILABLE);
+}
+
+static int
+savedeadletter(struct passwd *pw, FILE *in)
+{
+ char buffer[PATH_MAX];
+ FILE *fp;
+ char *buf = NULL;
+ size_t sz = 0;
+ ssize_t len;
+
+ (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
+
+ if (fseek(in, 0, SEEK_SET) != 0)
+ return 0;
+
+ if ((fp = fopen(buffer, "w")) == NULL)
+ return 0;
+
+ /* add From */
+ if (!msg.saw_from)
+ fprintf(fp, "From: %s%s<%s>\n",
+ msg.fromname ? msg.fromname : "",
+ msg.fromname ? " " : "",
+ msg.from);
+
+ /* add Date */
+ if (!msg.saw_date)
+ fprintf(fp, "Date: %s\n", time_to_text(timestamp));
+
+ if (msg.need_linesplit) {
+ /* we will always need to mime encode for long lines */
+ if (!msg.saw_mime_version)
+ fprintf(fp, "MIME-Version: 1.0\n");
+ if (!msg.saw_content_type)
+ fprintf(fp, "Content-Type: text/plain; "
+ "charset=unknown-8bit\n");
+ if (!msg.saw_content_disposition)
+ fprintf(fp, "Content-Disposition: inline\n");
+ if (!msg.saw_content_transfer_encoding)
+ fprintf(fp, "Content-Transfer-Encoding: "
+ "quoted-printable\n");
+ }
+
+ /* add separating newline */
+ if (msg.noheader)
+ fprintf(fp, "\n");
+
+ while ((len = getline(&buf, &sz, in)) != -1) {
+ if (buf[len - 1] == '\n')
+ buf[len - 1] = '\0';
+ fprintf(fp, "%s\n", buf);
+ }
+
+ free(buf);
+ fprintf(fp, "\n");
+ fclose(fp);
+ return 1;
+}