xref: /openbsd/usr.sbin/smtpd/enqueue.c (revision 510586ac)
1*510586acSclaudio /*	$OpenBSD: enqueue.c,v 1.122 2024/01/20 09:01:03 claudio Exp $	*/
2f607a12cSgilles 
3f607a12cSgilles /*
483d9e0c8Sjacekm  * Copyright (c) 2005 Henning Brauer <henning@bulabula.org>
5195a632dSjacekm  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
665c4fdfbSgilles  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7f607a12cSgilles  *
8f607a12cSgilles  * Permission to use, copy, modify, and distribute this software for any
9f607a12cSgilles  * purpose with or without fee is hereby granted, provided that the above
10f607a12cSgilles  * copyright notice and this permission notice appear in all copies.
11f607a12cSgilles  *
12f607a12cSgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13f607a12cSgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14f607a12cSgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15f607a12cSgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1683d9e0c8Sjacekm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
1783d9e0c8Sjacekm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
1883d9e0c8Sjacekm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19f607a12cSgilles  */
20f607a12cSgilles 
21f607a12cSgilles #include <ctype.h>
22f607a12cSgilles #include <err.h>
2392b2d8b8Shenning #include <errno.h>
24f607a12cSgilles #include <pwd.h>
25f607a12cSgilles #include <stdlib.h>
26f607a12cSgilles #include <string.h>
270dcffd0dSop #include <time.h>
28f607a12cSgilles #include <unistd.h>
29f607a12cSgilles 
30f607a12cSgilles #include "smtpd.h"
31f607a12cSgilles 
32f607a12cSgilles extern struct imsgbuf	*ibuf;
33f607a12cSgilles 
3483d9e0c8Sjacekm void usage(void);
35be925435Sgilles static void build_from(char *, struct passwd *);
36be925435Sgilles static int parse_message(FILE *, int, int, FILE *);
37be925435Sgilles static void parse_addr(char *, size_t, int);
38be925435Sgilles static void parse_addr_terminal(int);
39be925435Sgilles static char *qualify_addr(char *);
40be925435Sgilles static void rcpt_add(char *);
41be925435Sgilles static int open_connection(void);
42ec5f626bSgilles static int get_responses(FILE *, int);
43ceeebefeSbenno static int send_line(FILE *, int, char *, ...)
44ceeebefeSbenno     __attribute__((__format__ (printf, 3, 4)));
458351d18bSgilles static int enqueue_offline(int, char *[], FILE *, FILE *);
46ec5f626bSgilles static int savedeadletter(struct passwd *, FILE *);
47f70d44e6Seric 
488351d18bSgilles extern int srv_connected(void);
4983d9e0c8Sjacekm 
5083d9e0c8Sjacekm enum headerfields {
5183d9e0c8Sjacekm 	HDR_NONE,
5283d9e0c8Sjacekm 	HDR_FROM,
5383d9e0c8Sjacekm 	HDR_TO,
5483d9e0c8Sjacekm 	HDR_CC,
5583d9e0c8Sjacekm 	HDR_BCC,
5683d9e0c8Sjacekm 	HDR_SUBJECT,
5783d9e0c8Sjacekm 	HDR_DATE,
5835e36fb6Sgilles 	HDR_MSGID,
5935e36fb6Sgilles 	HDR_MIME_VERSION,
6035e36fb6Sgilles 	HDR_CONTENT_TYPE,
6135e36fb6Sgilles 	HDR_CONTENT_DISPOSITION,
6235e36fb6Sgilles 	HDR_CONTENT_TRANSFER_ENCODING,
6335e36fb6Sgilles 	HDR_USER_AGENT
6483d9e0c8Sjacekm };
6583d9e0c8Sjacekm 
6683d9e0c8Sjacekm struct {
6783d9e0c8Sjacekm 	char			*word;
6883d9e0c8Sjacekm 	enum headerfields	 type;
6983d9e0c8Sjacekm } keywords[] = {
7083d9e0c8Sjacekm 	{ "From:",			HDR_FROM },
7183d9e0c8Sjacekm 	{ "To:",			HDR_TO },
7283d9e0c8Sjacekm 	{ "Cc:",			HDR_CC },
7383d9e0c8Sjacekm 	{ "Bcc:",			HDR_BCC },
7483d9e0c8Sjacekm 	{ "Subject:",			HDR_SUBJECT },
7583d9e0c8Sjacekm 	{ "Date:",			HDR_DATE },
7635e36fb6Sgilles 	{ "Message-Id:",		HDR_MSGID },
7735e36fb6Sgilles 	{ "MIME-Version:",		HDR_MIME_VERSION },
7835e36fb6Sgilles 	{ "Content-Type:",		HDR_CONTENT_TYPE },
7935e36fb6Sgilles 	{ "Content-Disposition:",	HDR_CONTENT_DISPOSITION },
8035e36fb6Sgilles 	{ "Content-Transfer-Encoding:",	HDR_CONTENT_TRANSFER_ENCODING },
8135e36fb6Sgilles 	{ "User-Agent:",		HDR_USER_AGENT },
8283d9e0c8Sjacekm };
8383d9e0c8Sjacekm 
842ea25afeSeric #define	LINESPLIT		990
8583d9e0c8Sjacekm #define	SMTP_LINELEN		1000
8683d9e0c8Sjacekm #define	TIMEOUTMSG		"Timeout\n"
8783d9e0c8Sjacekm 
8883d9e0c8Sjacekm #define WSP(c)			(c == ' ' || c == '\t')
8983d9e0c8Sjacekm 
9083d9e0c8Sjacekm int		 verbose = 0;
914c5b19cfSsunil static char	 host[HOST_NAME_MAX+1];
9283d9e0c8Sjacekm char		*user = NULL;
9383d9e0c8Sjacekm time_t		 timestamp;
9483d9e0c8Sjacekm 
9583d9e0c8Sjacekm struct {
9683d9e0c8Sjacekm 	int	  fd;
9783d9e0c8Sjacekm 	char	 *from;
9883d9e0c8Sjacekm 	char	 *fromname;
9983d9e0c8Sjacekm 	char	**rcpts;
100fe95d8d0Seric 	char	 *dsn_notify;
101fe95d8d0Seric 	char	 *dsn_ret;
102fe95d8d0Seric 	char	 *dsn_envid;
10383d9e0c8Sjacekm 	int	  rcpt_cnt;
1042ea25afeSeric 	int	  need_linesplit;
10583d9e0c8Sjacekm 	int	  saw_date;
10683d9e0c8Sjacekm 	int	  saw_msgid;
10783d9e0c8Sjacekm 	int	  saw_from;
10835e36fb6Sgilles 	int	  saw_mime_version;
10935e36fb6Sgilles 	int	  saw_content_type;
11035e36fb6Sgilles 	int	  saw_content_disposition;
11135e36fb6Sgilles 	int	  saw_content_transfer_encoding;
11235e36fb6Sgilles 	int	  saw_user_agent;
113ec5f626bSgilles 	int	  noheader;
11483d9e0c8Sjacekm } msg;
11583d9e0c8Sjacekm 
11683d9e0c8Sjacekm struct {
117d2241734Schl 	uint		quote;
118d2241734Schl 	uint		comment;
119d2241734Schl 	uint		esc;
120d2241734Schl 	uint		brackets;
12183d9e0c8Sjacekm 	size_t		wpos;
12283d9e0c8Sjacekm 	char		buf[SMTP_LINELEN];
12383d9e0c8Sjacekm } pstate;
12483d9e0c8Sjacekm 
1252e7fd8aeSmartijn #define QP_TEST_WRAP(fp, buf, linelen, size)	do {			\
1262e7fd8aeSmartijn 	if (((linelen) += (size)) + 1 > 76) {				\
12787cc67beSeric 		fprintf((fp), "=\r\n");					\
1282e7fd8aeSmartijn 		if (buf[0] == '.')					\
1292e7fd8aeSmartijn 			fprintf((fp), ".");				\
1302e7fd8aeSmartijn 		(linelen) = (size);					\
1312e7fd8aeSmartijn 	}								\
1322e7fd8aeSmartijn } while (0)
1335bc4f8deSgilles 
1342e7fd8aeSmartijn /* RFC 2045 section 6.7 */
1352e7fd8aeSmartijn static void
qp_encoded_write(FILE * fp,char * buf)1362e7fd8aeSmartijn qp_encoded_write(FILE *fp, char *buf)
1372e7fd8aeSmartijn {
1382e7fd8aeSmartijn 	size_t linelen = 0;
1392e7fd8aeSmartijn 
1402e7fd8aeSmartijn 	for (;buf[0] != '\0' && buf[0] != '\n'; buf++) {
1412e7fd8aeSmartijn 		/*
1422e7fd8aeSmartijn 		 * Point 3: Any TAB (HT) or SPACE characters on an encoded line
1432e7fd8aeSmartijn 		 * MUST thus be followed on that line by a printable character.
1442e7fd8aeSmartijn 		 *
1452e7fd8aeSmartijn 		 * Ergo, only encode if the next character is EOL.
1462e7fd8aeSmartijn 		 */
1472e7fd8aeSmartijn 		if (buf[0] == ' ' || buf[0] == '\t') {
1482e7fd8aeSmartijn 			if (buf[1] == '\n') {
1492e7fd8aeSmartijn 				QP_TEST_WRAP(fp, buf, linelen, 3);
1500cfe015eSeric 				fprintf(fp, "=%2X", *buf & 0xff);
1512e7fd8aeSmartijn 			} else {
1522e7fd8aeSmartijn 				QP_TEST_WRAP(fp, buf, linelen, 1);
1535bc4f8deSgilles 				fprintf(fp, "%c", *buf & 0xff);
1545bc4f8deSgilles 			}
1552e7fd8aeSmartijn 		/*
1562e7fd8aeSmartijn 		 * Point 1, with exclusion of point 2, skip EBCDIC NOTE.
1572e7fd8aeSmartijn 		 * Do this after whitespace check, else they would match here.
1582e7fd8aeSmartijn 		 */
1592e7fd8aeSmartijn 		} else if (!((buf[0] >= 33 && buf[0] <= 60) ||
1602e7fd8aeSmartijn 		    (buf[0] >= 62 && buf[0] <= 126))) {
1612e7fd8aeSmartijn 			QP_TEST_WRAP(fp, buf, linelen, 3);
1620cfe015eSeric 			fprintf(fp, "=%2X", *buf & 0xff);
1632e7fd8aeSmartijn 		/* Point 2: 33 through 60 inclusive, and 62 through 126 */
1642e7fd8aeSmartijn 		} else {
1652e7fd8aeSmartijn 			QP_TEST_WRAP(fp, buf, linelen, 1);
16635e36fb6Sgilles 			fprintf(fp, "%c", *buf);
16735e36fb6Sgilles 		}
16835e36fb6Sgilles 	}
16987cc67beSeric 	fprintf(fp, "\r\n");
1702e7fd8aeSmartijn }
17135e36fb6Sgilles 
172f607a12cSgilles int
enqueue(int argc,char * argv[],FILE * ofp)1738351d18bSgilles enqueue(int argc, char *argv[], FILE *ofp)
174f607a12cSgilles {
175ec5f626bSgilles 	int			 i, ch, tflag = 0;
1761baa1adfSsunil 	char			*fake_from = NULL, *buf = NULL;
177f607a12cSgilles 	struct passwd		*pw;
178230bac0bSgilles 	FILE			*fp = NULL, *fout;
1791baa1adfSsunil 	size_t			 sz = 0, envid_sz = 0;
1801baa1adfSsunil 	ssize_t			 len;
18135e36fb6Sgilles 	char			*line;
182084579e0Smillert 	int			 inheaders = 1;
183f70d44e6Seric 	int			 save_argc;
184f70d44e6Seric 	char			**save_argv;
1852a157de5Sgilles 	int			 no_getlogin = 0;
186f607a12cSgilles 
187c1392a69Seric 	memset(&msg, 0, sizeof(msg));
18883d9e0c8Sjacekm 	time(&timestamp);
189f607a12cSgilles 
190f70d44e6Seric 	save_argc = argc;
191f70d44e6Seric 	save_argv = argv;
192f70d44e6Seric 
19305d60b42Sjacekm 	while ((ch = getopt(argc, argv,
194f6168539Sgilles 	    "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) {
195f607a12cSgilles 		switch (ch) {
196f607a12cSgilles 		case 'f':
19783d9e0c8Sjacekm 			fake_from = optarg;
198f607a12cSgilles 			break;
19983d9e0c8Sjacekm 		case 'F':
20083d9e0c8Sjacekm 			msg.fromname = optarg;
20183d9e0c8Sjacekm 			break;
202fe95d8d0Seric 		case 'N':
203fe95d8d0Seric 			msg.dsn_notify = optarg;
204fe95d8d0Seric 			break;
205f6168539Sgilles 		case 'r':
206f6168539Sgilles 			fake_from = optarg;
207f6168539Sgilles 			break;
208fe95d8d0Seric 		case 'R':
209fe95d8d0Seric 			msg.dsn_ret = optarg;
210fe95d8d0Seric 			break;
2112a157de5Sgilles 		case 'S':
2122a157de5Sgilles 			no_getlogin = 1;
2132a157de5Sgilles 			break;
21483d9e0c8Sjacekm 		case 't':
21583d9e0c8Sjacekm 			tflag = 1;
21683d9e0c8Sjacekm 			break;
21783d9e0c8Sjacekm 		case 'v':
21883d9e0c8Sjacekm 			verbose = 1;
21983d9e0c8Sjacekm 			break;
220fe95d8d0Seric 		case 'V':
221fe95d8d0Seric 			msg.dsn_envid = optarg;
222fe95d8d0Seric 			break;
22383d9e0c8Sjacekm 		/* all remaining: ignored, sendmail compat */
22405d60b42Sjacekm 		case 'A':
22583d9e0c8Sjacekm 		case 'B':
22683d9e0c8Sjacekm 		case 'b':
22783d9e0c8Sjacekm 		case 'E':
22883d9e0c8Sjacekm 		case 'e':
22983d9e0c8Sjacekm 		case 'i':
23005d60b42Sjacekm 		case 'L':
23183d9e0c8Sjacekm 		case 'm':
2328b05dd90Sgilles 		case 'o':
23383d9e0c8Sjacekm 		case 'p':
23483d9e0c8Sjacekm 		case 'x':
235f607a12cSgilles 			break;
23605d60b42Sjacekm 		case 'q':
237e5b07014Sgilles 			/* XXX: implement "process all now" */
238f70d44e6Seric 			return (EX_SOFTWARE);
239f607a12cSgilles 		default:
240f607a12cSgilles 			usage();
241f607a12cSgilles 		}
242f607a12cSgilles 	}
243f607a12cSgilles 
244f607a12cSgilles 	argc -= optind;
245f607a12cSgilles 	argv += optind;
246f607a12cSgilles 
247f70d44e6Seric 	if (getmailname(host, sizeof(host)) == -1)
24836e884f4Ssunil 		errx(EX_NOHOST, "getmailname");
2492a157de5Sgilles 	if (no_getlogin) {
2502a157de5Sgilles 		if ((pw = getpwuid(getuid())) == NULL)
2512a157de5Sgilles 			user = "anonymous";
2522a157de5Sgilles 		if (pw != NULL)
253118c16f3Sgilles 			user = xstrdup(pw->pw_name);
2542a157de5Sgilles 	}
2552a157de5Sgilles 	else {
25658c9f649Smillert 		uid_t ruid = getuid();
25758c9f649Smillert 
25858c9f649Smillert 		if ((user = getlogin()) != NULL && *user != '\0') {
25958c9f649Smillert 			if ((pw = getpwnam(user)) == NULL ||
26058c9f649Smillert 			    (ruid != 0 && ruid != pw->pw_uid))
26158c9f649Smillert 				pw = getpwuid(ruid);
26258c9f649Smillert 		} else if ((pw = getpwuid(ruid)) == NULL) {
26383d9e0c8Sjacekm 			user = "anonymous";
26458c9f649Smillert 		}
265118c16f3Sgilles 		user = xstrdup(pw ? pw->pw_name : user);
2662a157de5Sgilles 	}
267f607a12cSgilles 
26883d9e0c8Sjacekm 	build_from(fake_from, pw);
2697f5b54e4Sgilles 
27083d9e0c8Sjacekm 	while (argc > 0) {
27183d9e0c8Sjacekm 		rcpt_add(argv[0]);
27283d9e0c8Sjacekm 		argv++;
27383d9e0c8Sjacekm 		argc--;
274f607a12cSgilles 	}
275f607a12cSgilles 
276729f2a4cSmartijn 	if ((fp = tmpfile()) == NULL)
277729f2a4cSmartijn 		err(EX_UNAVAILABLE, "tmpfile");
278729f2a4cSmartijn 
279ec5f626bSgilles 	msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp);
280e2447230Sjacekm 
281e0764761Seric 	if (msg.rcpt_cnt == 0)
282f70d44e6Seric 		errx(EX_SOFTWARE, "no recipients");
283195a632dSjacekm 
284195a632dSjacekm 	/* init session */
285e2447230Sjacekm 	rewind(fp);
286195a632dSjacekm 
2878351d18bSgilles 	/* check if working in offline mode */
288f70d44e6Seric 	/* If the server is not running, enqueue the message offline */
289f70d44e6Seric 
290ee59291bSgilles 	if (!srv_connected()) {
291ee59291bSgilles 		if (pledge("stdio", NULL) == -1)
292ee59291bSgilles 			err(1, "pledge");
2934af06d43Smmcc 
2948351d18bSgilles 		return (enqueue_offline(save_argc, save_argv, fp, ofp));
295ee59291bSgilles 	}
296f70d44e6Seric 
297e0764761Seric 	if ((msg.fd = open_connection()) == -1)
298f70d44e6Seric 		errx(EX_UNAVAILABLE, "server too busy");
299195a632dSjacekm 
3007cf9226eSgilles 	if (pledge("stdio wpath cpath", NULL) == -1)
301ee59291bSgilles 		err(1, "pledge");
302ee59291bSgilles 
303e0764761Seric 	fout = fdopen(msg.fd, "a+");
304e0764761Seric 	if (fout == NULL)
305f70d44e6Seric 		err(EX_UNAVAILABLE, "fdopen");
306e0764761Seric 
307e0764761Seric 	/*
308e0764761Seric 	 * We need to call get_responses after every command because we don't
309e0764761Seric 	 * support PIPELINING on the server-side yet.
310e0764761Seric 	 */
311e0764761Seric 
312e0764761Seric 	/* banner */
313ec5f626bSgilles 	if (!get_responses(fout, 1))
314ec5f626bSgilles 		goto fail;
315e0764761Seric 
31687cc67beSeric 	if (!send_line(fout, verbose, "EHLO localhost\r\n"))
317084579e0Smillert 		goto fail;
318ec5f626bSgilles 	if (!get_responses(fout, 1))
319ec5f626bSgilles 		goto fail;
320e0764761Seric 
321fe95d8d0Seric 	if (msg.dsn_envid != NULL)
322fe95d8d0Seric 		envid_sz = strlen(msg.dsn_envid);
323fe95d8d0Seric 
32487cc67beSeric 	if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n",
325fe95d8d0Seric 	    msg.from,
326fe95d8d0Seric 	    msg.dsn_ret ? "RET=" : "",
327fe95d8d0Seric 	    msg.dsn_ret ? msg.dsn_ret : "",
328fe95d8d0Seric 	    envid_sz ? "ENVID=" : "",
329084579e0Smillert 	    envid_sz ? msg.dsn_envid : ""))
330084579e0Smillert 		goto fail;
331ec5f626bSgilles 	if (!get_responses(fout, 1))
332ec5f626bSgilles 		goto fail;
333e0764761Seric 
334e0764761Seric 	for (i = 0; i < msg.rcpt_cnt; i++) {
33587cc67beSeric 		if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n",
336fe95d8d0Seric 		    msg.rcpts[i],
337fe95d8d0Seric 		    msg.dsn_notify ? "NOTIFY=" : "",
338084579e0Smillert 		    msg.dsn_notify ? msg.dsn_notify : ""))
339084579e0Smillert 			goto fail;
340ec5f626bSgilles 		if (!get_responses(fout, 1))
341ec5f626bSgilles 			goto fail;
342e0764761Seric 	}
343e0764761Seric 
34487cc67beSeric 	if (!send_line(fout, verbose, "DATA\r\n"))
345084579e0Smillert 		goto fail;
346ec5f626bSgilles 	if (!get_responses(fout, 1))
347ec5f626bSgilles 		goto fail;
348195a632dSjacekm 
349195a632dSjacekm 	/* add From */
35087cc67beSeric 	if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n",
351084579e0Smillert 	    msg.fromname ? msg.fromname : "", msg.fromname ? " " : "",
352084579e0Smillert 	    msg.from))
353084579e0Smillert 		goto fail;
354195a632dSjacekm 
355195a632dSjacekm 	/* add Date */
35687cc67beSeric 	if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n",
357084579e0Smillert 	    time_to_text(timestamp)))
358084579e0Smillert 		goto fail;
359195a632dSjacekm 
3602ea25afeSeric 	if (msg.need_linesplit) {
36135e36fb6Sgilles 		/* we will always need to mime encode for long lines */
362084579e0Smillert 		if (!msg.saw_mime_version && !send_line(fout, 0,
36387cc67beSeric 		    "MIME-Version: 1.0\r\n"))
364084579e0Smillert 			goto fail;
365084579e0Smillert 		if (!msg.saw_content_type && !send_line(fout, 0,
36687cc67beSeric 		    "Content-Type: text/plain; charset=unknown-8bit\r\n"))
367084579e0Smillert 			goto fail;
368084579e0Smillert 		if (!msg.saw_content_disposition && !send_line(fout, 0,
36987cc67beSeric 		    "Content-Disposition: inline\r\n"))
370084579e0Smillert 			goto fail;
371084579e0Smillert 		if (!msg.saw_content_transfer_encoding && !send_line(fout, 0,
37287cc67beSeric 		    "Content-Transfer-Encoding: quoted-printable\r\n"))
373084579e0Smillert 			goto fail;
3742ea25afeSeric 	}
37535e36fb6Sgilles 
376195a632dSjacekm 	/* add separating newline */
377084579e0Smillert 	if (msg.noheader) {
37887cc67beSeric 		if (!send_line(fout, 0, "\r\n"))
379084579e0Smillert 			goto fail;
380084579e0Smillert 		inheaders = 0;
381084579e0Smillert 	}
382195a632dSjacekm 
383e0764761Seric 	for (;;) {
3841baa1adfSsunil 		if ((len = getline(&buf, &sz, fp)) == -1) {
3851baa1adfSsunil 			if (feof(fp))
386e0764761Seric 				break;
3871baa1adfSsunil 			else
3881baa1adfSsunil 				err(EX_UNAVAILABLE, "getline");
3891baa1adfSsunil 		}
3901baa1adfSsunil 
391e0764761Seric 		/* newlines have been normalized on first parsing */
392e0764761Seric 		if (buf[len-1] != '\n')
393f70d44e6Seric 			errx(EX_SOFTWARE, "expect EOL");
39487cc67beSeric 		len--;
395740da2c9Sjacekm 
39635e36fb6Sgilles 		if (buf[0] == '.') {
397084579e0Smillert 			if (fputc('.', fout) == EOF)
398084579e0Smillert 				goto fail;
39935e36fb6Sgilles 		}
40035e36fb6Sgilles 
40135e36fb6Sgilles 		line = buf;
40235e36fb6Sgilles 
40317f785b9Sgilles 		if (inheaders) {
40417f785b9Sgilles 			if (strncasecmp("from ", line, 5) == 0)
40517f785b9Sgilles 				continue;
40617f785b9Sgilles 			if (strncasecmp("return-path: ", line, 13) == 0)
40717f785b9Sgilles 				continue;
40817f785b9Sgilles 		}
40917f785b9Sgilles 
410ec5f626bSgilles 		if (msg.saw_content_transfer_encoding || msg.noheader ||
4117bdbba2fSeric 		    inheaders || !msg.need_linesplit) {
41287cc67beSeric 			if (!send_line(fout, 0, "%.*s\r\n", (int)len, line))
413084579e0Smillert 				goto fail;
414a1b2432dSgilles 			if (inheaders && buf[0] == '\n')
415a1b2432dSgilles 				inheaders = 0;
41635e36fb6Sgilles 			continue;
41735e36fb6Sgilles 		}
41835e36fb6Sgilles 
41935e36fb6Sgilles 		/* we don't have a content transfer encoding, use our default */
4202e7fd8aeSmartijn 		qp_encoded_write(fout, line);
421e0764761Seric 	}
4221baa1adfSsunil 	free(buf);
42387cc67beSeric 	if (!send_line(fout, verbose, ".\r\n"))
424084579e0Smillert 		goto fail;
425ec5f626bSgilles 	if (!get_responses(fout, 1))
426ec5f626bSgilles 		goto fail;
427f4ef9244Sjacekm 
42887cc67beSeric 	if (!send_line(fout, verbose, "QUIT\r\n"))
429084579e0Smillert 		goto fail;
430ec5f626bSgilles 	if (!get_responses(fout, 1))
431ec5f626bSgilles 		goto fail;
432e0764761Seric 
433fb9a2a56Sgilles 	fclose(fp);
434e0764761Seric 	fclose(fout);
435e0764761Seric 
436f70d44e6Seric 	exit(EX_OK);
437ec5f626bSgilles 
438ec5f626bSgilles fail:
439ec5f626bSgilles 	if (pw)
440ec5f626bSgilles 		savedeadletter(pw, fp);
441ec5f626bSgilles 	exit(EX_SOFTWARE);
442f4ef9244Sjacekm }
443f4ef9244Sjacekm 
444ec5f626bSgilles static int
get_responses(FILE * fin,int n)445e0764761Seric get_responses(FILE *fin, int n)
446f4ef9244Sjacekm {
4471baa1adfSsunil 	char	*buf = NULL;
4481baa1adfSsunil 	size_t	 sz = 0;
4491baa1adfSsunil 	ssize_t	 len;
4501baa1adfSsunil 	int	 e, ret = 0;
451f4ef9244Sjacekm 
452e0764761Seric 	fflush(fin);
453ec5f626bSgilles 	if ((e = ferror(fin))) {
454ec5f626bSgilles 		warnx("ferror: %d", e);
4551baa1adfSsunil 		goto err;
456ec5f626bSgilles 	}
457e0764761Seric 
458e0764761Seric 	while (n) {
4591baa1adfSsunil 		if ((len = getline(&buf, &sz, fin)) == -1) {
4601baa1adfSsunil 			if (ferror(fin)) {
4611baa1adfSsunil 				warn("getline");
4621baa1adfSsunil 				goto err;
4631baa1adfSsunil 			} else if (feof(fin))
464195a632dSjacekm 				break;
4651baa1adfSsunil 			else
4661baa1adfSsunil 				err(EX_UNAVAILABLE, "getline");
467ec5f626bSgilles 		}
468e0764761Seric 
469e0764761Seric 		/* account for \r\n linebreaks */
470e0764761Seric 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
471e0764761Seric 			buf[--len - 1] = '\n';
472e0764761Seric 
473ec5f626bSgilles 		if (len < 4) {
474ec5f626bSgilles 			warnx("bad response");
4751baa1adfSsunil 			goto err;
476ec5f626bSgilles 		}
477e0764761Seric 
478e0764761Seric 		if (verbose)
47918427eecStodd 			printf("<<< %.*s", (int)len, buf);
480e0764761Seric 
481e0764761Seric 		if (buf[3] == '-')
482e0764761Seric 			continue;
483ec5f626bSgilles 		if (buf[0] != '2' && buf[0] != '3') {
484ec5f626bSgilles 			warnx("command failed: %.*s", (int)len, buf);
4851baa1adfSsunil 			goto err;
486ec5f626bSgilles 		}
487e0764761Seric 		n--;
488195a632dSjacekm 	}
4891baa1adfSsunil 
4901baa1adfSsunil 	ret = 1;
4911baa1adfSsunil err:
4921baa1adfSsunil 	free(buf);
4931baa1adfSsunil 	return ret;
494f607a12cSgilles }
495f607a12cSgilles 
49618427eecStodd static int
send_line(FILE * fp,int v,char * fmt,...)49718427eecStodd send_line(FILE *fp, int v, char *fmt, ...)
49818427eecStodd {
499084579e0Smillert 	int ret = 0;
50018427eecStodd 	va_list ap;
50118427eecStodd 
50218427eecStodd 	va_start(ap, fmt);
503084579e0Smillert 	if (vfprintf(fp, fmt, ap) >= 0)
504084579e0Smillert 	    ret = 1;
50582614934Seric 	va_end(ap);
50682614934Seric 
507084579e0Smillert 	if (ret && v) {
50882614934Seric 		printf(">>> ");
509084579e0Smillert 		va_start(ap, fmt);
510084579e0Smillert 		vprintf(fmt, ap);
51118427eecStodd 		va_end(ap);
51282614934Seric 	}
51382614934Seric 
51418427eecStodd 	return (ret);
51518427eecStodd }
51618427eecStodd 
517be925435Sgilles static void
build_from(char * fake_from,struct passwd * pw)51883d9e0c8Sjacekm build_from(char *fake_from, struct passwd *pw)
51983d9e0c8Sjacekm {
52083d9e0c8Sjacekm 	char	*p;
52183d9e0c8Sjacekm 
52283d9e0c8Sjacekm 	if (fake_from == NULL)
52383d9e0c8Sjacekm 		msg.from = qualify_addr(user);
52483d9e0c8Sjacekm 	else {
52583d9e0c8Sjacekm 		if (fake_from[0] == '<') {
52683d9e0c8Sjacekm 			if (fake_from[strlen(fake_from) - 1] != '>')
52783d9e0c8Sjacekm 				errx(1, "leading < but no trailing >");
52883d9e0c8Sjacekm 			fake_from[strlen(fake_from) - 1] = 0;
529118c16f3Sgilles 			p = xstrdup(fake_from + 1);
53083d9e0c8Sjacekm 
53183d9e0c8Sjacekm 			msg.from = qualify_addr(p);
53283d9e0c8Sjacekm 			free(p);
53383d9e0c8Sjacekm 		} else
53483d9e0c8Sjacekm 			msg.from = qualify_addr(fake_from);
53583d9e0c8Sjacekm 	}
53683d9e0c8Sjacekm 
53783d9e0c8Sjacekm 	if (msg.fromname == NULL && fake_from == NULL && pw != NULL) {
5382b486ed1Sjacekm 		int	 len, apos;
539f607a12cSgilles 
54083d9e0c8Sjacekm 		len = strcspn(pw->pw_gecos, ",");
5412b486ed1Sjacekm 		if ((p = memchr(pw->pw_gecos, '&', len))) {
5422b486ed1Sjacekm 			apos = p - pw->pw_gecos;
5432b486ed1Sjacekm 			if (asprintf(&msg.fromname, "%.*s%s%.*s",
5442b486ed1Sjacekm 			    apos, pw->pw_gecos,
5452b486ed1Sjacekm 			    pw->pw_name,
5462b486ed1Sjacekm 			    len - apos - 1, p + 1) == -1)
547f607a12cSgilles 				err(1, NULL);
548fc3a8311Seric 			msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]);
5492b486ed1Sjacekm 		} else {
5502b486ed1Sjacekm 			if (asprintf(&msg.fromname, "%.*s", len,
5512b486ed1Sjacekm 			    pw->pw_gecos) == -1)
5522b486ed1Sjacekm 				err(1, NULL);
5532b486ed1Sjacekm 		}
554f607a12cSgilles 	}
555f607a12cSgilles }
55683d9e0c8Sjacekm 
557be925435Sgilles static int
parse_message(FILE * fin,int get_from,int tflag,FILE * fout)558e2447230Sjacekm parse_message(FILE *fin, int get_from, int tflag, FILE *fout)
55983d9e0c8Sjacekm {
5601baa1adfSsunil 	char	*buf = NULL;
5611baa1adfSsunil 	size_t	 sz = 0;
5621baa1adfSsunil 	ssize_t	 len;
563d2241734Schl 	uint	 i, cur = HDR_NONE;
564d2241734Schl 	uint	 header_seen = 0, header_done = 0;
56583d9e0c8Sjacekm 
566c1392a69Seric 	memset(&pstate, 0, sizeof(pstate));
56783d9e0c8Sjacekm 	for (;;) {
5681baa1adfSsunil 		if ((len = getline(&buf, &sz, fin)) == -1) {
5691baa1adfSsunil 			if (feof(fin))
57083d9e0c8Sjacekm 				break;
5711baa1adfSsunil 			else
5721baa1adfSsunil 				err(EX_UNAVAILABLE, "getline");
5731baa1adfSsunil 		}
57483d9e0c8Sjacekm 
57583d9e0c8Sjacekm 		/* account for \r\n linebreaks */
57683d9e0c8Sjacekm 		if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n')
57783d9e0c8Sjacekm 			buf[--len - 1] = '\n';
57883d9e0c8Sjacekm 
57983d9e0c8Sjacekm 		if (len == 1 && buf[0] == '\n')		/* end of header */
58083d9e0c8Sjacekm 			header_done = 1;
58183d9e0c8Sjacekm 
58283d9e0c8Sjacekm 		if (!WSP(buf[0])) {	/* whitespace -> continuation */
58383d9e0c8Sjacekm 			if (cur == HDR_FROM)
58483d9e0c8Sjacekm 				parse_addr_terminal(1);
58583d9e0c8Sjacekm 			if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)
58683d9e0c8Sjacekm 				parse_addr_terminal(0);
58783d9e0c8Sjacekm 			cur = HDR_NONE;
58883d9e0c8Sjacekm 		}
58983d9e0c8Sjacekm 
5902ea25afeSeric 		/* not really exact, if we are still in headers */
5912ea25afeSeric 		if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT)
5922ea25afeSeric 			msg.need_linesplit = 1;
5932ea25afeSeric 
59483d9e0c8Sjacekm 		for (i = 0; !header_done && cur == HDR_NONE &&
59567e51f35Seric 		    i < nitems(keywords); i++)
5961baa1adfSsunil 			if ((size_t)len > strlen(keywords[i].word) &&
59783d9e0c8Sjacekm 			    !strncasecmp(buf, keywords[i].word,
59883d9e0c8Sjacekm 			    strlen(keywords[i].word)))
59983d9e0c8Sjacekm 				cur = keywords[i].type;
60083d9e0c8Sjacekm 
60183d9e0c8Sjacekm 		if (cur != HDR_NONE)
60283d9e0c8Sjacekm 			header_seen = 1;
60383d9e0c8Sjacekm 
60483d9e0c8Sjacekm 		if (cur != HDR_BCC) {
605084579e0Smillert 			if (!send_line(fout, 0, "%.*s", (int)len, buf))
606e2447230Sjacekm 				err(1, "write error");
607084579e0Smillert 			if (buf[len - 1] != '\n') {
608084579e0Smillert 				if (fputc('\n', fout) == EOF)
609084579e0Smillert 					err(1, "write error");
610084579e0Smillert 			}
61183d9e0c8Sjacekm 		}
61283d9e0c8Sjacekm 
61383d9e0c8Sjacekm 		/*
61483d9e0c8Sjacekm 		 * using From: as envelope sender is not sendmail compatible,
61583d9e0c8Sjacekm 		 * but I really want it that way - maybe needs a knob
61683d9e0c8Sjacekm 		 */
61783d9e0c8Sjacekm 		if (cur == HDR_FROM) {
61883d9e0c8Sjacekm 			msg.saw_from++;
61983d9e0c8Sjacekm 			if (get_from)
62083d9e0c8Sjacekm 				parse_addr(buf, len, 1);
62183d9e0c8Sjacekm 		}
62283d9e0c8Sjacekm 
62383d9e0c8Sjacekm 		if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC))
62483d9e0c8Sjacekm 			parse_addr(buf, len, 0);
62583d9e0c8Sjacekm 
62683d9e0c8Sjacekm 		if (cur == HDR_DATE)
62783d9e0c8Sjacekm 			msg.saw_date++;
62883d9e0c8Sjacekm 		if (cur == HDR_MSGID)
62983d9e0c8Sjacekm 			msg.saw_msgid++;
63035e36fb6Sgilles 		if (cur == HDR_MIME_VERSION)
63135e36fb6Sgilles 			msg.saw_mime_version = 1;
63235e36fb6Sgilles 		if (cur == HDR_CONTENT_TYPE)
63335e36fb6Sgilles 			msg.saw_content_type = 1;
63435e36fb6Sgilles 		if (cur == HDR_CONTENT_DISPOSITION)
63535e36fb6Sgilles 			msg.saw_content_disposition = 1;
63635e36fb6Sgilles 		if (cur == HDR_CONTENT_TRANSFER_ENCODING)
63735e36fb6Sgilles 			msg.saw_content_transfer_encoding = 1;
63835e36fb6Sgilles 		if (cur == HDR_USER_AGENT)
63935e36fb6Sgilles 			msg.saw_user_agent = 1;
64083d9e0c8Sjacekm 	}
64183d9e0c8Sjacekm 
6421baa1adfSsunil 	free(buf);
64383d9e0c8Sjacekm 	return (!header_seen);
64483d9e0c8Sjacekm }
64583d9e0c8Sjacekm 
646be925435Sgilles static void
parse_addr(char * s,size_t len,int is_from)64783d9e0c8Sjacekm parse_addr(char *s, size_t len, int is_from)
64883d9e0c8Sjacekm {
64983d9e0c8Sjacekm 	size_t	 pos = 0;
65083d9e0c8Sjacekm 	int	 terminal = 0;
65183d9e0c8Sjacekm 
65283d9e0c8Sjacekm 	/* unless this is a continuation... */
65383d9e0c8Sjacekm 	if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') {
65483d9e0c8Sjacekm 		/* ... skip over everything before the ':' */
65583d9e0c8Sjacekm 		for (; pos < len && s[pos] != ':'; pos++)
65683d9e0c8Sjacekm 			;	/* nothing */
65783d9e0c8Sjacekm 		/* ... and check & reset parser state */
65883d9e0c8Sjacekm 		parse_addr_terminal(is_from);
65983d9e0c8Sjacekm 	}
66083d9e0c8Sjacekm 
66183d9e0c8Sjacekm 	/* skip over ':' ',' ';' and whitespace */
66283d9e0c8Sjacekm 	for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' ||
66383d9e0c8Sjacekm 	    s[pos] == ',' || s[pos] == ';'); pos++)
66483d9e0c8Sjacekm 		;	/* nothing */
66583d9e0c8Sjacekm 
66683d9e0c8Sjacekm 	for (; pos < len; pos++) {
66783d9e0c8Sjacekm 		if (!pstate.esc && !pstate.quote && s[pos] == '(')
66883d9e0c8Sjacekm 			pstate.comment++;
66983d9e0c8Sjacekm 		if (!pstate.comment && !pstate.esc && s[pos] == '"')
67083d9e0c8Sjacekm 			pstate.quote = !pstate.quote;
67183d9e0c8Sjacekm 
67283d9e0c8Sjacekm 		if (!pstate.comment && !pstate.quote && !pstate.esc) {
67383d9e0c8Sjacekm 			if (s[pos] == ':') {	/* group */
67483d9e0c8Sjacekm 				for (pos++; pos < len && WSP(s[pos]); pos++)
67583d9e0c8Sjacekm 					;	/* nothing */
67683d9e0c8Sjacekm 				pstate.wpos = 0;
67783d9e0c8Sjacekm 			}
67883d9e0c8Sjacekm 			if (s[pos] == '\n' || s[pos] == '\r')
67983d9e0c8Sjacekm 				break;
68083d9e0c8Sjacekm 			if (s[pos] == ',' || s[pos] == ';') {
68183d9e0c8Sjacekm 				terminal = 1;
68283d9e0c8Sjacekm 				break;
68383d9e0c8Sjacekm 			}
68483d9e0c8Sjacekm 			if (s[pos] == '<') {
68583d9e0c8Sjacekm 				pstate.brackets = 1;
68683d9e0c8Sjacekm 				pstate.wpos = 0;
68783d9e0c8Sjacekm 			}
68883d9e0c8Sjacekm 			if (pstate.brackets && s[pos] == '>')
68983d9e0c8Sjacekm 				terminal = 1;
69083d9e0c8Sjacekm 		}
69183d9e0c8Sjacekm 
69283d9e0c8Sjacekm 		if (!pstate.comment && !terminal && (!(!(pstate.quote ||
69383d9e0c8Sjacekm 		    pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) {
69483d9e0c8Sjacekm 			if (pstate.wpos >= sizeof(pstate.buf))
69583d9e0c8Sjacekm 				errx(1, "address exceeds buffer size");
69683d9e0c8Sjacekm 			pstate.buf[pstate.wpos++] = s[pos];
69783d9e0c8Sjacekm 		}
69883d9e0c8Sjacekm 
69983d9e0c8Sjacekm 		if (!pstate.quote && pstate.comment && s[pos] == ')')
70083d9e0c8Sjacekm 			pstate.comment--;
70183d9e0c8Sjacekm 
702640f55f5Ssunil 		if (!pstate.esc && !pstate.comment && s[pos] == '\\')
70383d9e0c8Sjacekm 			pstate.esc = 1;
70483d9e0c8Sjacekm 		else
70583d9e0c8Sjacekm 			pstate.esc = 0;
70683d9e0c8Sjacekm 	}
70783d9e0c8Sjacekm 
70883d9e0c8Sjacekm 	if (terminal)
70983d9e0c8Sjacekm 		parse_addr_terminal(is_from);
71083d9e0c8Sjacekm 
71183d9e0c8Sjacekm 	for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++)
71283d9e0c8Sjacekm 		;	/* nothing */
71383d9e0c8Sjacekm 
71483d9e0c8Sjacekm 	if (pos < len)
71583d9e0c8Sjacekm 		parse_addr(s + pos, len - pos, is_from);
71683d9e0c8Sjacekm }
71783d9e0c8Sjacekm 
718be925435Sgilles static void
parse_addr_terminal(int is_from)71983d9e0c8Sjacekm parse_addr_terminal(int is_from)
72083d9e0c8Sjacekm {
72183d9e0c8Sjacekm 	if (pstate.comment || pstate.quote || pstate.esc)
72283d9e0c8Sjacekm 		errx(1, "syntax error in address");
72383d9e0c8Sjacekm 	if (pstate.wpos) {
72483d9e0c8Sjacekm 		if (pstate.wpos >= sizeof(pstate.buf))
72583d9e0c8Sjacekm 			errx(1, "address exceeds buffer size");
72683d9e0c8Sjacekm 		pstate.buf[pstate.wpos] = '\0';
72783d9e0c8Sjacekm 		if (is_from)
72883d9e0c8Sjacekm 			msg.from = qualify_addr(pstate.buf);
72983d9e0c8Sjacekm 		else
73083d9e0c8Sjacekm 			rcpt_add(pstate.buf);
73183d9e0c8Sjacekm 		pstate.wpos = 0;
73283d9e0c8Sjacekm 	}
73383d9e0c8Sjacekm }
73483d9e0c8Sjacekm 
735be925435Sgilles static char *
qualify_addr(char * in)73683d9e0c8Sjacekm qualify_addr(char *in)
73783d9e0c8Sjacekm {
73883d9e0c8Sjacekm 	char	*out;
73983d9e0c8Sjacekm 
7405d0beaf0Sjacekm 	if (strlen(in) > 0 && strchr(in, '@') == NULL) {
74183d9e0c8Sjacekm 		if (asprintf(&out, "%s@%s", in, host) == -1)
74283d9e0c8Sjacekm 			err(1, "qualify asprintf");
74383d9e0c8Sjacekm 	} else
744118c16f3Sgilles 		out = xstrdup(in);
74583d9e0c8Sjacekm 
74683d9e0c8Sjacekm 	return (out);
74783d9e0c8Sjacekm }
74883d9e0c8Sjacekm 
749be925435Sgilles static void
rcpt_add(char * addr)75083d9e0c8Sjacekm rcpt_add(char *addr)
75183d9e0c8Sjacekm {
75283d9e0c8Sjacekm 	void	*nrcpts;
753fc01a027Stodd 	char	*p;
754fc01a027Stodd 	int	n;
755fc01a027Stodd 
756fc01a027Stodd 	n = 1;
757fc01a027Stodd 	p = addr;
758fc01a027Stodd 	while ((p = strchr(p, ',')) != NULL) {
759fc01a027Stodd 		n++;
760fc01a027Stodd 		p++;
761fc01a027Stodd 	}
76283d9e0c8Sjacekm 
76371671d2bSderaadt 	if ((nrcpts = reallocarray(msg.rcpts,
76471671d2bSderaadt 	    msg.rcpt_cnt + n, sizeof(char *))) == NULL)
76583d9e0c8Sjacekm 		err(1, "rcpt_add realloc");
76683d9e0c8Sjacekm 	msg.rcpts = nrcpts;
767fc01a027Stodd 
768fc01a027Stodd 	while (n--) {
769fc01a027Stodd 		if ((p = strchr(addr, ',')) != NULL)
770fc01a027Stodd 			*p++ = '\0';
77183d9e0c8Sjacekm 		msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr);
7721c6ac251Seric 		if (p == NULL)
7731c6ac251Seric 			break;
774fc01a027Stodd 		addr = p;
775fc01a027Stodd 	}
77683d9e0c8Sjacekm }
77783d9e0c8Sjacekm 
778be925435Sgilles static int
open_connection(void)77983d9e0c8Sjacekm open_connection(void)
78083d9e0c8Sjacekm {
78183d9e0c8Sjacekm 	struct imsg	imsg;
78283d9e0c8Sjacekm 	int		fd;
78383d9e0c8Sjacekm 	int		n;
78483d9e0c8Sjacekm 
785aa1d5973Seric 	imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0);
78683d9e0c8Sjacekm 
78783d9e0c8Sjacekm 	while (ibuf->w.queued)
788ffb968afSkrw 		if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
78983d9e0c8Sjacekm 			err(1, "write error");
79083d9e0c8Sjacekm 
79183d9e0c8Sjacekm 	while (1) {
792369c9387Sclaudio 		if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
79383d9e0c8Sjacekm 			errx(1, "imsg_read error");
79483d9e0c8Sjacekm 		if (n == 0)
79583d9e0c8Sjacekm 			errx(1, "pipe closed");
79683d9e0c8Sjacekm 
79783d9e0c8Sjacekm 		if ((n = imsg_get(ibuf, &imsg)) == -1)
79883d9e0c8Sjacekm 			errx(1, "imsg_get error");
79983d9e0c8Sjacekm 		if (n == 0)
80083d9e0c8Sjacekm 			continue;
80183d9e0c8Sjacekm 
80221dda8f7Sjacekm 		switch (imsg.hdr.type) {
80321dda8f7Sjacekm 		case IMSG_CTL_OK:
80421dda8f7Sjacekm 			break;
80521dda8f7Sjacekm 		case IMSG_CTL_FAIL:
80621dda8f7Sjacekm 			errx(1, "server disallowed submission request");
80721dda8f7Sjacekm 		default:
80821dda8f7Sjacekm 			errx(1, "unexpected imsg reply type");
80921dda8f7Sjacekm 		}
81021dda8f7Sjacekm 
811*510586acSclaudio 		fd = imsg_get_fd(&imsg);
81283d9e0c8Sjacekm 		imsg_free(&imsg);
81383d9e0c8Sjacekm 
81483d9e0c8Sjacekm 		break;
81583d9e0c8Sjacekm 	}
81683d9e0c8Sjacekm 
81783d9e0c8Sjacekm 	return fd;
81883d9e0c8Sjacekm }
81983d9e0c8Sjacekm 
820f70d44e6Seric static int
enqueue_offline(int argc,char * argv[],FILE * ifile,FILE * ofile)8218351d18bSgilles enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile)
82225a5298cSjacekm {
8238351d18bSgilles 	int	i, ch;
824299c4efeSeric 
82525a5298cSjacekm 	for (i = 1; i < argc; i++) {
82625a5298cSjacekm 		if (strchr(argv[i], '|') != NULL) {
82725a5298cSjacekm 			warnx("%s contains illegal character", argv[i]);
8286507df71Sgilles 			ftruncate(fileno(ofile), 0);
829f70d44e6Seric 			exit(EX_SOFTWARE);
83025a5298cSjacekm 		}
831da01648bSmillert 		if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0)
832da01648bSmillert 			goto write_error;
83325a5298cSjacekm 	}
83425a5298cSjacekm 
835da01648bSmillert 	if (fputc('\n', ofile) == EOF)
836da01648bSmillert 		goto write_error;
83725a5298cSjacekm 
838da01648bSmillert 	while ((ch = fgetc(ifile)) != EOF) {
839da01648bSmillert 		if (fputc(ch, ofile) == EOF)
840da01648bSmillert 			goto write_error;
84125a5298cSjacekm 	}
84225a5298cSjacekm 
843f70d44e6Seric 	if (ferror(ifile)) {
84425a5298cSjacekm 		warn("read error");
8456507df71Sgilles 		ftruncate(fileno(ofile), 0);
846f70d44e6Seric 		exit(EX_UNAVAILABLE);
84725a5298cSjacekm 	}
84825a5298cSjacekm 
849da01648bSmillert 	if (fclose(ofile) == EOF)
850da01648bSmillert 		goto write_error;
85125a5298cSjacekm 
852f70d44e6Seric 	return (EX_TEMPFAIL);
853da01648bSmillert write_error:
854da01648bSmillert 	warn("write error");
855da01648bSmillert 	ftruncate(fileno(ofile), 0);
856da01648bSmillert 	exit(EX_UNAVAILABLE);
85725a5298cSjacekm }
858ec5f626bSgilles 
859ec5f626bSgilles static int
savedeadletter(struct passwd * pw,FILE * in)860ec5f626bSgilles savedeadletter(struct passwd *pw, FILE *in)
861ec5f626bSgilles {
862953aae25Sderaadt 	char	 buffer[PATH_MAX];
863ec5f626bSgilles 	FILE	*fp;
8641baa1adfSsunil 	char	*buf = NULL;
8651baa1adfSsunil 	size_t	 sz = 0;
8661baa1adfSsunil 	ssize_t	 len;
867ec5f626bSgilles 
868ec5f626bSgilles 	(void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir);
869ec5f626bSgilles 
870ec5f626bSgilles 	if (fseek(in, 0, SEEK_SET) != 0)
871ec5f626bSgilles 		return 0;
872ec5f626bSgilles 
8734f7aa44fSgilles 	if ((fp = fopen(buffer, "w")) == NULL)
874ec5f626bSgilles 		return 0;
875ec5f626bSgilles 
876ec5f626bSgilles 	/* add From */
877ec5f626bSgilles 	if (!msg.saw_from)
878ec5f626bSgilles 		fprintf(fp, "From: %s%s<%s>\n",
879ec5f626bSgilles 		    msg.fromname ? msg.fromname : "",
880ec5f626bSgilles 		    msg.fromname ? " " : "",
881ec5f626bSgilles 		    msg.from);
882ec5f626bSgilles 
883ec5f626bSgilles 	/* add Date */
884ec5f626bSgilles 	if (!msg.saw_date)
885ec5f626bSgilles 		fprintf(fp, "Date: %s\n", time_to_text(timestamp));
886ec5f626bSgilles 
887ec5f626bSgilles 	if (msg.need_linesplit) {
888ec5f626bSgilles 		/* we will always need to mime encode for long lines */
889ec5f626bSgilles 		if (!msg.saw_mime_version)
890ec5f626bSgilles 			fprintf(fp, "MIME-Version: 1.0\n");
891ec5f626bSgilles 		if (!msg.saw_content_type)
892ec5f626bSgilles 			fprintf(fp, "Content-Type: text/plain; "
893ec5f626bSgilles 			    "charset=unknown-8bit\n");
894ec5f626bSgilles 		if (!msg.saw_content_disposition)
895ec5f626bSgilles 			fprintf(fp, "Content-Disposition: inline\n");
896ec5f626bSgilles 		if (!msg.saw_content_transfer_encoding)
897ec5f626bSgilles 			fprintf(fp, "Content-Transfer-Encoding: "
898ec5f626bSgilles 			    "quoted-printable\n");
899ec5f626bSgilles 	}
900ec5f626bSgilles 
901ec5f626bSgilles 	/* add separating newline */
902ec5f626bSgilles 	if (msg.noheader)
903ec5f626bSgilles 		fprintf(fp, "\n");
904ec5f626bSgilles 
9051baa1adfSsunil 	while ((len = getline(&buf, &sz, in)) != -1) {
906ec5f626bSgilles 		if (buf[len - 1] == '\n')
907ec5f626bSgilles 			buf[len - 1] = '\0';
908ec5f626bSgilles 		fprintf(fp, "%s\n", buf);
909ec5f626bSgilles 	}
910ec5f626bSgilles 
9111baa1adfSsunil 	free(buf);
912ec5f626bSgilles 	fprintf(fp, "\n");
913ec5f626bSgilles 	fclose(fp);
914ec5f626bSgilles 	return 1;
915ec5f626bSgilles }
916