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