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