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