1/*	$OpenBSD: enqueue.c,v 1.122 2024/01/20 09:01:03 claudio Exp $	*/
2
3/*
4 * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
5 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
6 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7 *
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
11 *
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
17 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
18 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 */
20
21#include <ctype.h>
22#include <err.h>
23#include <errno.h>
24#include <pwd.h>
25#include <stdlib.h>
26#include <string.h>
27#include <time.h>
28#include <unistd.h>
29
30#include "smtpd.h"
31
32extern struct imsgbuf	*ibuf;
33
34void usage(void);
35static void build_from(char *, struct passwd *);
36static int parse_message(FILE *, int, int, FILE *);
37static void parse_addr(char *, size_t, int);
38static void parse_addr_terminal(int);
39static char *qualify_addr(char *);
40static void rcpt_add(char *);
41static int open_connection(void);
42static int get_responses(FILE *, int);
43static int send_line(FILE *, int, char *, ...)
44    __attribute__((__format__ (printf, 3, 4)));
45static int enqueue_offline(int, char *[], FILE *, FILE *);
46static int savedeadletter(struct passwd *, FILE *);
47
48extern int srv_connected(void);
49
50enum headerfields {
51	HDR_NONE,
52	HDR_FROM,
53	HDR_TO,
54	HDR_CC,
55	HDR_BCC,
56	HDR_SUBJECT,
57	HDR_DATE,
58	HDR_MSGID,
59	HDR_MIME_VERSION,
60	HDR_CONTENT_TYPE,
61	HDR_CONTENT_DISPOSITION,
62	HDR_CONTENT_TRANSFER_ENCODING,
63	HDR_USER_AGENT
64};
65
66struct {
67	char			*word;
68	enum headerfields	 type;
69} keywords[] = {
70	{ "From:",			HDR_FROM },
71	{ "To:",			HDR_TO },
72	{ "Cc:",			HDR_CC },
73	{ "Bcc:",			HDR_BCC },
74	{ "Subject:",			HDR_SUBJECT },
75	{ "Date:",			HDR_DATE },
76	{ "Message-Id:",		HDR_MSGID },
77	{ "MIME-Version:",		HDR_MIME_VERSION },
78	{ "Content-Type:",		HDR_CONTENT_TYPE },
79	{ "Content-Disposition:",	HDR_CONTENT_DISPOSITION },
80	{ "Content-Transfer-Encoding:",	HDR_CONTENT_TRANSFER_ENCODING },
81	{ "User-Agent:",		HDR_USER_AGENT },
82};
83
84#define	LINESPLIT		990
85#define	SMTP_LINELEN		1000
86#define	TIMEOUTMSG		"Timeout\n"
87
88#define WSP(c)			(c == ' ' || c == '\t')
89
90int		 verbose = 0;
91static char	 host[HOST_NAME_MAX+1];
92char		*user = NULL;
93time_t		 timestamp;
94
95struct {
96	int	  fd;
97	char	 *from;
98	char	 *fromname;
99	char	**rcpts;
100	char	 *dsn_notify;
101	char	 *dsn_ret;
102	char	 *dsn_envid;
103	int	  rcpt_cnt;
104	int	  need_linesplit;
105	int	  saw_date;
106	int	  saw_msgid;
107	int	  saw_from;
108	int	  saw_mime_version;
109	int	  saw_content_type;
110	int	  saw_content_disposition;
111	int	  saw_content_transfer_encoding;
112	int	  saw_user_agent;
113	int	  noheader;
114} msg;
115
116struct {
117	uint		quote;
118	uint		comment;
119	uint		esc;
120	uint		brackets;
121	size_t		wpos;
122	char		buf[SMTP_LINELEN];
123} pstate;
124
125#define QP_TEST_WRAP(fp, buf, linelen, size)	do {			\
126	if (((linelen) += (size)) + 1 > 76) {				\
127		fprintf((fp), "=\r\n");					\
128		if (buf[0] == '.')					\
129			fprintf((fp), ".");				\
130		(linelen) = (size);					\
131	}								\
132} while (0)
133
134/* RFC 2045 section 6.7 */
135static void
136qp_encoded_write(FILE *fp, char *buf)
137{
138	size_t linelen = 0;
139
140	for (;buf[0] != '\0' && buf[0] != '\n'; buf++) {
141		/*
142		 * Point 3: Any TAB (HT) or SPACE characters on an encoded line
143		 * MUST thus be followed on that line by a printable character.
144		 *
145		 * Ergo, only encode if the next character is EOL.
146		 */
147		if (buf[0] == ' ' || buf[0] == '\t') {
148			if (buf[1] == '\n') {
149				QP_TEST_WRAP(fp, buf, linelen, 3);
150				fprintf(fp, "=%2X", *buf & 0xff);
151			} else {
152				QP_TEST_WRAP(fp, buf, linelen, 1);
153				fprintf(fp, "%c", *buf & 0xff);
154			}
155		/*
156		 * Point 1, with exclusion of point 2, skip EBCDIC NOTE.
157		 * Do this after whitespace check, else they would match here.
158		 */
159		} else if (!((buf[0] >= 33 && buf[0] <= 60) ||
160		    (buf[0] >= 62 && buf[0] <= 126))) {
161			QP_TEST_WRAP(fp, buf, linelen, 3);
162			fprintf(fp, "=%2X", *buf & 0xff);
163		/* Point 2: 33 through 60 inclusive, and 62 through 126 */
164		} else {
165			QP_TEST_WRAP(fp, buf, linelen, 1);
166			fprintf(fp, "%c", *buf);
167		}
168	}
169	fprintf(fp, "\r\n");
170}
171
172int
173enqueue(int argc, char *argv[], FILE *ofp)
174{
175	int			 i, ch, tflag = 0;
176	char			*fake_from = NULL, *buf = NULL;
177	struct passwd		*pw;
178	FILE			*fp = NULL, *fout;
179	size_t			 sz = 0, envid_sz = 0;
180	ssize_t			 len;
181	char			*line;
182	int			 inheaders = 1;
183	int			 save_argc;
184	char			**save_argv;
185	int			 no_getlogin = 0;
186
187	memset(&msg, 0, sizeof(msg));
188	time(&timestamp);
189
190	save_argc = argc;
191	save_argv = argv;
192
193	while ((ch = getopt(argc, argv,
194	    "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) {
195		switch (ch) {
196		case 'f':
197			fake_from = optarg;
198			break;
199		case 'F':
200			msg.fromname = optarg;
201			break;
202		case 'N':
203			msg.dsn_notify = optarg;
204			break;
205		case 'r':
206			fake_from = optarg;
207			break;
208		case 'R':
209			msg.dsn_ret = optarg;
210			break;
211		case 'S':
212			no_getlogin = 1;
213			break;
214		case 't':
215			tflag = 1;
216			break;
217		case 'v':
218			verbose = 1;
219			break;
220		case 'V':
221			msg.dsn_envid = optarg;
222			break;
223		/* all remaining: ignored, sendmail compat */
224		case 'A':
225		case 'B':
226		case 'b':
227		case 'E':
228		case 'e':
229		case 'i':
230		case 'L':
231		case 'm':
232		case 'o':
233		case 'p':
234		case 'x':
235			break;
236		case 'q':
237			/* XXX: implement "process all now" */
238			return (EX_SOFTWARE);
239		default:
240			usage();
241		}
242	}
243
244	argc -= optind;
245	argv += optind;
246
247	if (getmailname(host, sizeof(host)) == -1)
248		errx(EX_NOHOST, "getmailname");
249	if (no_getlogin) {
250		if ((pw = getpwuid(getuid())) == NULL)
251			user = "anonymous";
252		if (pw != NULL)
253			user = xstrdup(pw->pw_name);
254	}
255	else {
256		uid_t ruid = getuid();
257
258		if ((user = getlogin()) != NULL && *user != '\0') {
259			if ((pw = getpwnam(user)) == NULL ||
260			    (ruid != 0 && ruid != pw->pw_uid))
261				pw = getpwuid(ruid);
262		} else if ((pw = getpwuid(ruid)) == NULL) {
263			user = "anonymous";
264		}
265		user = xstrdup(pw ? pw->pw_name : user);
266	}
267
268	build_from(fake_from, pw);
269
270	while (argc > 0) {
271		rcpt_add(argv[0]);
272		argv++;
273		argc--;
274	}
275
276	if ((fp = tmpfile()) == NULL)
277		err(EX_UNAVAILABLE, "tmpfile");
278
279	msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
280
281	if (msg.rcpt_cnt == 0)
282		errx(EX_SOFTWARE, "no recipients");
283
284	/* init session */
285	rewind(fp);
286
287	/* check if working in offline mode */
288	/* If the server is not running, enqueue the message offline */
289
290	if (!srv_connected()) {
291		if (pledge("stdio", NULL) == -1)
292			err(1, "pledge");
293
294		return (enqueue_offline(save_argc, save_argv, fp, ofp));
295	}
296
297	if ((msg.fd = open_connection()) == -1)
298		errx(EX_UNAVAILABLE, "server too busy");
299
300	if (pledge("stdio wpath cpath", NULL) == -1)
301		err(1, "pledge");
302
303	fout = fdopen(msg.fd, "a+");
304	if (fout == NULL)
305		err(EX_UNAVAILABLE, "fdopen");
306
307	/*
308	 * We need to call get_responses after every command because we don't
309	 * support PIPELINING on the server-side yet.
310	 */
311
312	/* banner */
313	if (!get_responses(fout, 1))
314		goto fail;
315
316	if (!send_line(fout, verbose, "EHLO localhost\r\n"))
317		goto fail;
318	if (!get_responses(fout, 1))
319		goto fail;
320
321	if (msg.dsn_envid != NULL)
322		envid_sz = strlen(msg.dsn_envid);
323
324	if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n",
325	    msg.from,
326	    msg.dsn_ret ? "RET=" : "",
327	    msg.dsn_ret ? msg.dsn_ret : "",
328	    envid_sz ? "ENVID=" : "",
329	    envid_sz ? msg.dsn_envid : ""))
330		goto fail;
331	if (!get_responses(fout, 1))
332		goto fail;
333
334	for (i = 0; i < msg.rcpt_cnt; i++) {
335		if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n",
336		    msg.rcpts[i],
337		    msg.dsn_notify ? "NOTIFY=" : "",
338		    msg.dsn_notify ? msg.dsn_notify : ""))
339			goto fail;
340		if (!get_responses(fout, 1))
341			goto fail;
342	}
343
344	if (!send_line(fout, verbose, "DATA\r\n"))
345		goto fail;
346	if (!get_responses(fout, 1))
347		goto fail;
348
349	/* add From */
350	if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n",
351	    msg.fromname ? msg.fromname : "", msg.fromname ? " " : "",
352	    msg.from))
353		goto fail;
354
355	/* add Date */
356	if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n",
357	    time_to_text(timestamp)))
358		goto fail;
359
360	if (msg.need_linesplit) {
361		/* we will always need to mime encode for long lines */
362		if (!msg.saw_mime_version && !send_line(fout, 0,
363		    "MIME-Version: 1.0\r\n"))
364			goto fail;
365		if (!msg.saw_content_type && !send_line(fout, 0,
366		    "Content-Type: text/plain; charset=unknown-8bit\r\n"))
367			goto fail;
368		if (!msg.saw_content_disposition && !send_line(fout, 0,
369		    "Content-Disposition: inline\r\n"))
370			goto fail;
371		if (!msg.saw_content_transfer_encoding && !send_line(fout, 0,
372		    "Content-Transfer-Encoding: quoted-printable\r\n"))
373			goto fail;
374	}
375
376	/* add separating newline */
377	if (msg.noheader) {
378		if (!send_line(fout, 0, "\r\n"))
379			goto fail;
380		inheaders = 0;
381	}
382
383	for (;;) {
384		if ((len = getline(&buf, &sz, fp)) == -1) {
385			if (feof(fp))
386				break;
387			else
388				err(EX_UNAVAILABLE, "getline");
389		}
390
391		/* newlines have been normalized on first parsing */
392		if (buf[len-1] != '\n')
393			errx(EX_SOFTWARE, "expect EOL");
394		len--;
395
396		if (buf[0] == '.') {
397			if (fputc('.', fout) == EOF)
398				goto fail;
399		}
400
401		line = buf;
402
403		if (inheaders) {
404			if (strncasecmp("from ", line, 5) == 0)
405				continue;
406			if (strncasecmp("return-path: ", line, 13) == 0)
407				continue;
408		}
409
410		if (msg.saw_content_transfer_encoding || msg.noheader ||
411		    inheaders || !msg.need_linesplit) {
412			if (!send_line(fout, 0, "%.*s\r\n", (int)len, line))
413				goto fail;
414			if (inheaders && buf[0] == '\n')
415				inheaders = 0;
416			continue;
417		}
418
419		/* we don't have a content transfer encoding, use our default */
420		qp_encoded_write(fout, line);
421	}
422	free(buf);
423	if (!send_line(fout, verbose, ".\r\n"))
424		goto fail;
425	if (!get_responses(fout, 1))
426		goto fail;
427
428	if (!send_line(fout, verbose, "QUIT\r\n"))
429		goto fail;
430	if (!get_responses(fout, 1))
431		goto fail;
432
433	fclose(fp);
434	fclose(fout);
435
436	exit(EX_OK);
437
438fail:
439	if (pw)
440		savedeadletter(pw, fp);
441	exit(EX_SOFTWARE);
442}
443
444static int
445get_responses(FILE *fin, int n)
446{
447	char	*buf = NULL;
448	size_t	 sz = 0;
449	ssize_t	 len;
450	int	 e, ret = 0;
451
452	fflush(fin);
453	if ((e = ferror(fin))) {
454		warnx("ferror: %d", e);
455		goto err;
456	}
457
458	while (n) {
459		if ((len = getline(&buf, &sz, fin)) == -1) {
460			if (ferror(fin)) {
461				warn("getline");
462				goto err;
463			} else if (feof(fin))
464				break;
465			else
466				err(EX_UNAVAILABLE, "getline");
467		}
468
469		/* account for \r\n linebreaks */
470		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
471			buf[--len - 1] = '\n';
472
473		if (len < 4) {
474			warnx("bad response");
475			goto err;
476		}
477
478		if (verbose)
479			printf("<<< %.*s", (int)len, buf);
480
481		if (buf[3] == '-')
482			continue;
483		if (buf[0] != '2' && buf[0] != '3') {
484			warnx("command failed: %.*s", (int)len, buf);
485			goto err;
486		}
487		n--;
488	}
489
490	ret = 1;
491err:
492	free(buf);
493	return ret;
494}
495
496static int
497send_line(FILE *fp, int v, char *fmt, ...)
498{
499	int ret = 0;
500	va_list ap;
501
502	va_start(ap, fmt);
503	if (vfprintf(fp, fmt, ap) >= 0)
504	    ret = 1;
505	va_end(ap);
506
507	if (ret && v) {
508		printf(">>> ");
509		va_start(ap, fmt);
510		vprintf(fmt, ap);
511		va_end(ap);
512	}
513
514	return (ret);
515}
516
517static void
518build_from(char *fake_from, struct passwd *pw)
519{
520	char	*p;
521
522	if (fake_from == NULL)
523		msg.from = qualify_addr(user);
524	else {
525		if (fake_from[0] == '<') {
526			if (fake_from[strlen(fake_from) - 1] != '>')
527				errx(1, "leading < but no trailing >");
528			fake_from[strlen(fake_from) - 1] = 0;
529			p = xstrdup(fake_from + 1);
530
531			msg.from = qualify_addr(p);
532			free(p);
533		} else
534			msg.from = qualify_addr(fake_from);
535	}
536
537	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
538		int	 len, apos;
539
540		len = strcspn(pw->pw_gecos, ",");
541		if ((p = memchr(pw->pw_gecos, '&', len))) {
542			apos = p - pw->pw_gecos;
543			if (asprintf(&msg.fromname, "%.*s%s%.*s",
544			    apos, pw->pw_gecos,
545			    pw->pw_name,
546			    len - apos - 1, p + 1) == -1)
547				err(1, NULL);
548			msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]);
549		} else {
550			if (asprintf(&msg.fromname, "%.*s", len,
551			    pw->pw_gecos) == -1)
552				err(1, NULL);
553		}
554	}
555}
556
557static int
558parse_message(FILE *fin, int get_from, int tflag, FILE *fout)
559{
560	char	*buf = NULL;
561	size_t	 sz = 0;
562	ssize_t	 len;
563	uint	 i, cur = HDR_NONE;
564	uint	 header_seen = 0, header_done = 0;
565
566	memset(&pstate, 0, sizeof(pstate));
567	for (;;) {
568		if ((len = getline(&buf, &sz, fin)) == -1) {
569			if (feof(fin))
570				break;
571			else
572				err(EX_UNAVAILABLE, "getline");
573		}
574
575		/* account for \r\n linebreaks */
576		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
577			buf[--len - 1] = '\n';
578
579		if (len == 1 && buf[0] == '\n')		/* end of header */
580			header_done = 1;
581
582		if (!WSP(buf[0])) {	/* whitespace -> continuation */
583			if (cur == HDR_FROM)
584				parse_addr_terminal(1);
585			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
586				parse_addr_terminal(0);
587			cur = HDR_NONE;
588		}
589
590		/* not really exact, if we are still in headers */
591		if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT)
592			msg.need_linesplit = 1;
593
594		for (i = 0; !header_done && cur == HDR_NONE &&
595		    i < nitems(keywords); i++)
596			if ((size_t)len > strlen(keywords[i].word) &&
597			    !strncasecmp(buf, keywords[i].word,
598			    strlen(keywords[i].word)))
599				cur = keywords[i].type;
600
601		if (cur != HDR_NONE)
602			header_seen = 1;
603
604		if (cur != HDR_BCC) {
605			if (!send_line(fout, 0, "%.*s", (int)len, buf))
606				err(1, "write error");
607			if (buf[len - 1] != '\n') {
608				if (fputc('\n', fout) == EOF)
609					err(1, "write error");
610			}
611		}
612
613		/*
614		 * using From: as envelope sender is not sendmail compatible,
615		 * but I really want it that way - maybe needs a knob
616		 */
617		if (cur == HDR_FROM) {
618			msg.saw_from++;
619			if (get_from)
620				parse_addr(buf, len, 1);
621		}
622
623		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
624			parse_addr(buf, len, 0);
625
626		if (cur == HDR_DATE)
627			msg.saw_date++;
628		if (cur == HDR_MSGID)
629			msg.saw_msgid++;
630		if (cur == HDR_MIME_VERSION)
631			msg.saw_mime_version = 1;
632		if (cur == HDR_CONTENT_TYPE)
633			msg.saw_content_type = 1;
634		if (cur == HDR_CONTENT_DISPOSITION)
635			msg.saw_content_disposition = 1;
636		if (cur == HDR_CONTENT_TRANSFER_ENCODING)
637			msg.saw_content_transfer_encoding = 1;
638		if (cur == HDR_USER_AGENT)
639			msg.saw_user_agent = 1;
640	}
641
642	free(buf);
643	return (!header_seen);
644}
645
646static void
647parse_addr(char *s, size_t len, int is_from)
648{
649	size_t	 pos = 0;
650	int	 terminal = 0;
651
652	/* unless this is a continuation... */
653	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
654		/* ... skip over everything before the ':' */
655		for (; pos < len && s[pos] != ':'; pos++)
656			;	/* nothing */
657		/* ... and check & reset parser state */
658		parse_addr_terminal(is_from);
659	}
660
661	/* skip over ':' ',' ';' and whitespace */
662	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
663	    s[pos] == ',' || s[pos] == ';'); pos++)
664		;	/* nothing */
665
666	for (; pos < len; pos++) {
667		if (!pstate.esc && !pstate.quote && s[pos] == '(')
668			pstate.comment++;
669		if (!pstate.comment && !pstate.esc && s[pos] == '"')
670			pstate.quote = !pstate.quote;
671
672		if (!pstate.comment && !pstate.quote && !pstate.esc) {
673			if (s[pos] == ':') {	/* group */
674				for (pos++; pos < len && WSP(s[pos]); pos++)
675					;	/* nothing */
676				pstate.wpos = 0;
677			}
678			if (s[pos] == '\n' || s[pos] == '\r')
679				break;
680			if (s[pos] == ',' || s[pos] == ';') {
681				terminal = 1;
682				break;
683			}
684			if (s[pos] == '<') {
685				pstate.brackets = 1;
686				pstate.wpos = 0;
687			}
688			if (pstate.brackets && s[pos] == '>')
689				terminal = 1;
690		}
691
692		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
693		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
694			if (pstate.wpos >= sizeof(pstate.buf))
695				errx(1, "address exceeds buffer size");
696			pstate.buf[pstate.wpos++] = s[pos];
697		}
698
699		if (!pstate.quote && pstate.comment && s[pos] == ')')
700			pstate.comment--;
701
702		if (!pstate.esc && !pstate.comment && s[pos] == '\\')
703			pstate.esc = 1;
704		else
705			pstate.esc = 0;
706	}
707
708	if (terminal)
709		parse_addr_terminal(is_from);
710
711	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
712		;	/* nothing */
713
714	if (pos < len)
715		parse_addr(s + pos, len - pos, is_from);
716}
717
718static void
719parse_addr_terminal(int is_from)
720{
721	if (pstate.comment || pstate.quote || pstate.esc)
722		errx(1, "syntax error in address");
723	if (pstate.wpos) {
724		if (pstate.wpos >= sizeof(pstate.buf))
725			errx(1, "address exceeds buffer size");
726		pstate.buf[pstate.wpos] = '\0';
727		if (is_from)
728			msg.from = qualify_addr(pstate.buf);
729		else
730			rcpt_add(pstate.buf);
731		pstate.wpos = 0;
732	}
733}
734
735static char *
736qualify_addr(char *in)
737{
738	char	*out;
739
740	if (strlen(in) > 0 && strchr(in, '@') == NULL) {
741		if (asprintf(&out, "%s@%s", in, host) == -1)
742			err(1, "qualify asprintf");
743	} else
744		out = xstrdup(in);
745
746	return (out);
747}
748
749static void
750rcpt_add(char *addr)
751{
752	void	*nrcpts;
753	char	*p;
754	int	n;
755
756	n = 1;
757	p = addr;
758	while ((p = strchr(p, ',')) != NULL) {
759		n++;
760		p++;
761	}
762
763	if ((nrcpts = reallocarray(msg.rcpts,
764	    msg.rcpt_cnt + n, sizeof(char *))) == NULL)
765		err(1, "rcpt_add realloc");
766	msg.rcpts = nrcpts;
767
768	while (n--) {
769		if ((p = strchr(addr, ',')) != NULL)
770			*p++ = '\0';
771		msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
772		if (p == NULL)
773			break;
774		addr = p;
775	}
776}
777
778static int
779open_connection(void)
780{
781	struct imsg	imsg;
782	int		fd;
783	int		n;
784
785	imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0);
786
787	while (ibuf->w.queued)
788		if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
789			err(1, "write error");
790
791	while (1) {
792		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
793			errx(1, "imsg_read error");
794		if (n == 0)
795			errx(1, "pipe closed");
796
797		if ((n = imsg_get(ibuf, &imsg)) == -1)
798			errx(1, "imsg_get error");
799		if (n == 0)
800			continue;
801
802		switch (imsg.hdr.type) {
803		case IMSG_CTL_OK:
804			break;
805		case IMSG_CTL_FAIL:
806			errx(1, "server disallowed submission request");
807		default:
808			errx(1, "unexpected imsg reply type");
809		}
810
811		fd = imsg_get_fd(&imsg);
812		imsg_free(&imsg);
813
814		break;
815	}
816
817	return fd;
818}
819
820static int
821enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
822{
823	int	i, ch;
824
825	for (i = 1; i < argc; i++) {
826		if (strchr(argv[i], '|') != NULL) {
827			warnx("%s contains illegal character", argv[i]);
828			ftruncate(fileno(ofile), 0);
829			exit(EX_SOFTWARE);
830		}
831		if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
832			goto write_error;
833	}
834
835	if (fputc('\n', ofile) == EOF)
836		goto write_error;
837
838	while ((ch = fgetc(ifile)) != EOF) {
839		if (fputc(ch, ofile) == EOF)
840			goto write_error;
841	}
842
843	if (ferror(ifile)) {
844		warn("read error");
845		ftruncate(fileno(ofile), 0);
846		exit(EX_UNAVAILABLE);
847	}
848
849	if (fclose(ofile) == EOF)
850		goto write_error;
851
852	return (EX_TEMPFAIL);
853write_error:
854	warn("write error");
855	ftruncate(fileno(ofile), 0);
856	exit(EX_UNAVAILABLE);
857}
858
859static int
860savedeadletter(struct passwd *pw, FILE *in)
861{
862	char	 buffer[PATH_MAX];
863	FILE	*fp;
864	char	*buf = NULL;
865	size_t	 sz = 0;
866	ssize_t	 len;
867
868	(void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
869
870	if (fseek(in, 0, SEEK_SET) != 0)
871		return 0;
872
873	if ((fp = fopen(buffer, "w")) == NULL)
874		return 0;
875
876	/* add From */
877	if (!msg.saw_from)
878		fprintf(fp, "From: %s%s<%s>\n",
879		    msg.fromname ? msg.fromname : "",
880		    msg.fromname ? " " : "",
881		    msg.from);
882
883	/* add Date */
884	if (!msg.saw_date)
885		fprintf(fp, "Date: %s\n", time_to_text(timestamp));
886
887	if (msg.need_linesplit) {
888		/* we will always need to mime encode for long lines */
889		if (!msg.saw_mime_version)
890			fprintf(fp, "MIME-Version: 1.0\n");
891		if (!msg.saw_content_type)
892			fprintf(fp, "Content-Type: text/plain; "
893			    "charset=unknown-8bit\n");
894		if (!msg.saw_content_disposition)
895			fprintf(fp, "Content-Disposition: inline\n");
896		if (!msg.saw_content_transfer_encoding)
897			fprintf(fp, "Content-Transfer-Encoding: "
898			    "quoted-printable\n");
899	}
900
901	/* add separating newline */
902	if (msg.noheader)
903		fprintf(fp, "\n");
904
905	while ((len = getline(&buf, &sz, in)) != -1) {
906		if (buf[len - 1] == '\n')
907			buf[len - 1] = '\0';
908		fprintf(fp, "%s\n", buf);
909	}
910
911	free(buf);
912	fprintf(fp, "\n");
913	fclose(fp);
914	return 1;
915}
916