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