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