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