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