xref: /illumos-gate/usr/src/cmd/mailx/collect.c (revision 1e56f352c1c208679012bca47d552e127f5b1072)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 1985, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*
30  * University Copyright- Copyright (c) 1982, 1986, 1988
31  * The Regents of the University of California
32  * All Rights Reserved
33  *
34  * University Acknowledgment- Portions of this document are derived from
35  * software developed by the University of California, Berkeley, and its
36  * contributors.
37  */
38 
39 /*
40  * mailx -- a modified version of a University of California at Berkeley
41  *	mail program
42  *
43  * Collect input from standard input, handling
44  * ~ escapes.
45  */
46 
47 #include "rcv.h"
48 #include <locale.h>
49 
50 #ifdef SIGCONT
51 static void	collcont(int);
52 #endif
53 static void	collrub(int s);
54 static void	cpout(char *str, FILE *ofd);
55 static int	exwrite(char name[], FILE *ibuf);
56 static int	forward(char ms[], FILE *obuf, int f);
57 static void	intack(int);
58 static int	forward(char ms[], FILE *obuf, int f);
59 static FILE	*mesedit(FILE *ibuf, FILE *obuf, int c, struct header *hp);
60 static FILE	*mespipe(FILE *ibuf, FILE *obuf, char cmd[]);
61 static void	resetsigs(int resethup);
62 static int	stripnulls(char *linebuf, int nread);
63 static void	xhalt(void);
64 static char	**Xaddone(char **hf, char news[]);
65 static int	tabputs(const char *line, FILE *obuf);
66 
67 /*
68  * Read a message from standard output and return a read file to it
69  * or NULL on error.
70  */
71 
72 /*
73  * The following hokiness with global variables is so that on
74  * receipt of an interrupt signal, the partial message can be salted
75  * away on dead.letter.  The output file must be available to flush,
76  * and the input to read.  Several open files could be saved all through
77  * mailx if stdio allowed simultaneous read/write access.
78  */
79 
80 static void		(*savesig)(int);	/* Previous SIGINT value */
81 static void		(*savehup)(int);	/* Previous SIGHUP value */
82 #ifdef SIGCONT
83 static void		(*savecont)(int);	/* Previous SIGCONT value */
84 #endif
85 static FILE		*newi;		/* File for saving away */
86 static FILE		*newo;		/* Output side of same */
87 static int		ignintr;	/* Ignore interrups */
88 static int		hadintr;	/* Have seen one SIGINT so far */
89 static struct header	*savehp;
90 static jmp_buf		coljmp;		/* To get back to work */
91 
92 FILE *
93 collect(struct header *hp)
94 {
95 	FILE *ibuf, *fbuf, *obuf;
96 	int escape, eof;
97 	long lc, cc;
98 	int c, t;
99 	int hdrs;
100 	char linebuf[LINESIZE+1], *cp;
101 	char *iprompt;
102 	int inhead;
103 	void (*sigpipe)(int), (*sigint)(int);
104 	int fd = -1;
105 
106 	noreset++;
107 	ibuf = obuf = NULL;
108 	newi = newo = NULL;
109 	if ((fd = open(tempMail, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
110 	(obuf = fdopen(fd, "w")) == NULL) {
111 		perror(tempMail);
112 		goto err;
113 	}
114 	newo = obuf;
115 	if ((ibuf = fopen(tempMail, "r")) == NULL) {
116 		perror(tempMail);
117 		newo = NULL;
118 		fclose(obuf);
119 		goto err;
120 	}
121 	newi = ibuf;
122 	removefile(tempMail);
123 
124 	ignintr = (int)value("ignore");
125 	hadintr = 1;
126 	inhead = 1;
127 	savehp = hp;
128 # ifdef VMUNIX
129 	if ((savesig = sigset(SIGINT, SIG_IGN)) != SIG_IGN)
130 		sigset(SIGINT, ignintr ? intack : collrub), sigblock(sigmask(SIGINT));
131 	if ((savehup = sigset(SIGHUP, SIG_IGN)) != SIG_IGN)
132 		sigset(SIGHUP, collrub), sigblock(sigmask(SIGHUP));
133 # else /* VMUNIX */
134 # ifdef OLD_BSD_SIGS
135 	if ((savesig = sigset(SIGINT, SIG_IGN)) != SIG_IGN)
136 		sigset(SIGINT, ignintr ? intack : collrub);
137 	if ((savehup = sigset(SIGHUP, SIG_IGN)) != SIG_IGN)
138 		sigset(SIGHUP, collrub);
139 # else
140 	if ((savesig = sigset(SIGINT, SIG_IGN)) != SIG_IGN) {
141 		sigset_t mask;
142 
143 		sigemptyset(&mask);
144 		sigaddset(&mask, SIGINT);
145 		sigset(SIGINT, ignintr ? intack : collrub);
146 		sigprocmask(SIG_BLOCK, &mask, NULL);
147 	}
148 	if ((savehup = sigset(SIGHUP, SIG_IGN)) != SIG_IGN) {
149 		sigset_t mask;
150 
151 		sigemptyset(&mask);
152 		sigaddset(&mask, SIGHUP);
153 		sigset(SIGHUP, collrub);
154 		sigprocmask(SIG_BLOCK, &mask, NULL);
155 	}
156 # endif
157 # endif /* VMUNIX */
158 #ifdef SIGCONT
159 	savecont = sigset(SIGCONT, collcont);
160 #endif
161 	/*
162 	 * If we are going to prompt for subject/cc/bcc,
163 	 * refrain from printing a newline after
164 	 * the headers (since some people mind).
165 	 */
166 
167 	if (hp->h_subject == NOSTR) {
168 		hp->h_subject = sflag;
169 		sflag = NOSTR;
170 	}
171 	if (hp->h_cc == NOSTR) {
172 		hp->h_cc = cflag;
173 		cflag = NOSTR;
174 	}
175 	if (hp->h_bcc == NOSTR) {
176 		hp->h_bcc = bflag;
177 		bflag = NOSTR;
178 	}
179 	t = GMASK;
180 	hdrs = 0;
181 	if (intty && !tflag) {
182 		if (hp->h_to == NOSTR)
183 			hdrs |= GTO;
184 		if (hp->h_subject == NOSTR && value("asksub"))
185 			hdrs |= GSUBJECT;
186 		if (hp->h_cc == NOSTR && value("askcc"))
187 			hdrs |= GCC;
188 		if (hp->h_bcc == NOSTR && value("askbcc"))
189 			hdrs |= GBCC;
190 		if (hdrs)
191 			t &= ~GNL;
192 	}
193 	if (hp->h_seq != 0) {
194 		puthead(hp, stdout, t, 0);
195 		fflush(stdout);
196 	}
197 	if (setjmp(coljmp))
198 		goto err;
199 	escape = SENDESC;
200 	if ((cp = value("escape")) != NOSTR)
201 		escape = *cp;
202 	eof = 0;
203 	if ((cp = value("MAILX_HEAD")) != NOSTR) {
204 	      cpout( cp, obuf);
205 	      if (isatty(fileno(stdin)))
206 		    cpout( cp, stdout);
207 	}
208 	iprompt = value("iprompt");
209 	fflush(obuf);
210 	hadintr = 0;
211 	for (;;) {
212 		int nread, hasnulls;
213 # ifdef VMUNIX
214 		int omask = sigblock(0) &~ (sigmask(SIGINT)|sigmask(SIGHUP));
215 # else
216 # ifndef OLD_BSD_SIGS
217 		sigset_t omask;
218 		sigprocmask(0, NULL, &omask);
219 		sigdelset(&omask, SIGINT);
220 		sigdelset(&omask, SIGHUP);
221 # endif
222 # endif
223 
224 		setjmp(coljmp);
225 # ifdef VMUNIX
226 		sigsetmask(omask);
227 # else /* VMUNIX */
228 # ifdef OLD_BSD_SIGS
229 		sigrelse(SIGINT);
230 		sigrelse(SIGHUP);
231 # else
232 		sigprocmask(SIG_SETMASK, &omask, NULL);
233 # endif
234 # endif /* VMUNIX */
235 		if (intty && !tflag && outtty && iprompt)
236 			fputs(iprompt, stdout);
237 		flush();
238 		if (hdrs) {
239 			grabh(hp, hdrs, 1);
240 			hdrs = 0;
241 			continue;
242 		}
243 		if ((nread = getaline(linebuf,LINESIZE,stdin,&hasnulls)) == 0) {
244 			if (intty && value("ignoreeof") != NOSTR) {
245 				if (++eof > 35)
246 					break;
247 				printf(gettext(
248 				    "Use \".\" to terminate letter\n"));
249 				continue;
250 			}
251 			break;
252 		}
253 		eof = 0;
254 		hadintr = 0;
255 		if (intty && equal(".\n", linebuf) &&
256 		    (value("dot") != NOSTR || value("ignoreeof") != NOSTR))
257 			break;
258 		/*
259 		 * If -t, scan text for headers.
260 		 */
261 		if (tflag) {
262 			char *cp2;
263 
264 			if (!inhead) {
265 			writeit:
266 				if (write(fileno(obuf),linebuf,nread) != nread)
267 					goto werr;
268 				continue;
269 			}
270 			if (linebuf[0] == '\n') {
271 				/* got blank line after header, ignore it */
272 				inhead = 0;
273 				continue;
274 			}
275 			if (!headerp(linebuf)) {
276 				/* got non-header line, save it */
277 				inhead = 0;
278 				goto writeit;
279 			}
280 			if (hasnulls)
281 				nread = stripnulls(linebuf, nread);
282 			for (;;) {
283 				char line2[LINESIZE];
284 
285 				c = getc(stdin);
286 				ungetc(c, stdin);
287 				if (!isspace(c) || c == '\n')
288 					break;
289 				if (readline(stdin, line2) < 0)
290 					break;
291 				for (cp2 = line2; *cp2 != 0 && isspace(*cp2);
292 				    cp2++)
293 					;
294 				if (strlen(linebuf) + strlen(cp2) >=
295 				    (unsigned)LINESIZE-2)
296 					break;
297 				cp = &linebuf[strlen(linebuf)];
298 				while (cp > linebuf &&
299 				    (isspace(cp[-1]) || cp[-1] == '\\'))
300 					cp--;
301 				*cp++ = ' ';
302 				strcpy(cp, cp2);
303 			}
304 			if ((c = strlen(linebuf)) > 0) {
305 				cp = &linebuf[c-1];
306 				while (cp > linebuf && isspace(*cp))
307 					cp--;
308 				*++cp = 0;
309 			}
310 			if (ishfield(linebuf, "to"))
311 				hp->h_to = addto(hp->h_to, hcontents(linebuf));
312 			else if (ishfield(linebuf, "subject"))
313 				hp->h_subject =
314 				    addone(hp->h_subject, hcontents(linebuf));
315 			else if (ishfield(linebuf, "cc"))
316 				hp->h_cc = addto(hp->h_cc, hcontents(linebuf));
317 			else if (ishfield(linebuf, "bcc"))
318 				hp->h_bcc =
319 				    addto(hp->h_bcc, hcontents(linebuf));
320 			else if (ishfield(linebuf, "default-options"))
321 				hp->h_defopt =
322 				    addone(hp->h_defopt, hcontents(linebuf));
323 			else
324 				hp->h_others = Xaddone(hp->h_others, linebuf);
325 			hp->h_seq++;
326 			continue;
327 		}
328 		if ((linebuf[0] != escape) || (rflag != NOSTR) ||
329 		    (!intty && !(int)value("escapeok"))) {
330 			if (write(fileno(obuf),linebuf,nread) != nread)
331 				goto werr;
332 			continue;
333 		}
334 		/*
335 		 * On double escape, just send the single one.
336 		 */
337 		if ((nread > 1) && (linebuf[1] == escape)) {
338 			if (write(fileno(obuf),linebuf+1,nread-1) != (nread-1))
339 				goto werr;
340 			continue;
341 		}
342 		if (hasnulls)
343 			nread = stripnulls(linebuf, nread);
344 		c = linebuf[1];
345 		linebuf[nread - 1] = '\0';
346 		switch (c) {
347 		default:
348 			/*
349 			 * Otherwise, it's an error.
350 			 */
351 			printf(gettext("Unknown tilde escape.\n"));
352 			break;
353 
354 		case 'a':
355 		case 'A':
356 			/*
357 			 * autograph; sign the letter.
358 			 */
359 
360 			if (cp = value(c=='a' ? "sign":"Sign")) {
361 			      if (*cp)
362 			          cpout( cp, obuf);
363 			      if (isatty(fileno(stdin))) {
364 			          if (*cp)
365 				      cpout( cp, stdout);
366 			    }
367 			}
368 
369 			break;
370 
371 		case 'i':
372 			/*
373 			 * insert string
374 			 */
375 			for (cp = &linebuf[2]; any(*cp, " \t"); cp++)
376 				;
377 			if (*cp)
378 				cp = value(cp);
379 			if (cp != NOSTR) {
380 				if (*cp)
381 				    cpout(cp, obuf);
382 				if (isatty(fileno(stdout))) {
383 					if (*cp)
384 					    cpout(cp, stdout);
385 				}
386 			}
387 			break;
388 
389 		case '!':
390 			/*
391 			 * Shell escape, send the balance of the
392 			 * line to sh -c.
393 			 */
394 
395 			shell(&linebuf[2]);
396 			break;
397 
398 		case ':':
399 		case '_':
400 			/*
401 			 * Escape to command mode, but be nice!
402 			 */
403 
404 			execute(&linebuf[2], 1);
405 			iprompt = value("iprompt");
406 			if (cp = value("escape"))
407 				escape = *cp;
408 			printf(gettext("(continue)\n"));
409 			break;
410 
411 		case '.':
412 			/*
413 			 * Simulate end of file on input.
414 			 */
415 			goto eofl;
416 
417 		case 'q':
418 		case 'Q':
419 			/*
420 			 * Force a quit of sending mail.
421 			 * Act like an interrupt happened.
422 			 */
423 
424 			hadintr++;
425 			collrub(SIGINT);
426 			exit(1);
427 			/* NOTREACHED */
428 
429 		case 'x':
430 			xhalt();
431 			break; 	/* not reached */
432 
433 		case 'h':
434 			/*
435 			 * Grab a bunch of headers.
436 			 */
437 			if (!intty || !outtty) {
438 				printf(gettext("~h: no can do!?\n"));
439 				break;
440 			}
441 			grabh(hp, GMASK, (int)value("bsdcompat"));
442 			printf(gettext("(continue)\n"));
443 			break;
444 
445 		case 't':
446 			/*
447 			 * Add to the To list.
448 			 */
449 
450 			hp->h_to = addto(hp->h_to, &linebuf[2]);
451 			hp->h_seq++;
452 			break;
453 
454 		case 's':
455 			/*
456 			 * Set the Subject list.
457 			 */
458 
459 			cp = &linebuf[2];
460 			while (any(*cp, " \t"))
461 				cp++;
462 			hp->h_subject = savestr(cp);
463 			hp->h_seq++;
464 			break;
465 
466 		case 'c':
467 			/*
468 			 * Add to the CC list.
469 			 */
470 
471 			hp->h_cc = addto(hp->h_cc, &linebuf[2]);
472 			hp->h_seq++;
473 			break;
474 
475 		case 'b':
476 			/*
477 			 * Add stuff to blind carbon copies list.
478 			 */
479 			hp->h_bcc = addto(hp->h_bcc, &linebuf[2]);
480 			hp->h_seq++;
481 			break;
482 
483 		case 'R':
484 			hp->h_defopt = addone(hp->h_defopt, myname);
485 			hp->h_seq++;
486 			fprintf(stderr, gettext("Return receipt marked.\n"));
487 			receipt_flg = 1;
488 			break;
489 
490 		case 'd':
491 			copy(Getf("DEAD"), &linebuf[2]);
492 			/* FALLTHROUGH */
493 
494 		case '<':
495 		case 'r': {
496 			int	ispip;
497 			/*
498 			 * Invoke a file:
499 			 * Search for the file name,
500 			 * then open it and copy the contents to obuf.
501 			 *
502 			 * if name begins with '!', read from a command
503 			 */
504 
505 			cp = &linebuf[2];
506 			while (any(*cp, " \t"))
507 				cp++;
508 			if (*cp == '\0') {
509 				printf(gettext("Interpolate what file?\n"));
510 				break;
511 			}
512 			if (*cp=='!') {
513 				/* take input from a command */
514 				ispip = 1;
515 				if ((fbuf = npopen(++cp, "r"))==NULL) {
516 					perror("");
517 					break;
518 				}
519 				sigint = sigset(SIGINT, SIG_IGN);
520 			} else {
521 				ispip = 0;
522 				cp = expand(cp);
523 				if (cp == NOSTR)
524 					break;
525 				if (isdir(cp)) {
526 					printf(gettext("%s: directory\n"), cp);
527 					break;
528 				}
529 				if ((fbuf = fopen(cp, "r")) == NULL) {
530 					perror(cp);
531 					break;
532 				}
533 			}
534 			printf("\"%s\" ", cp);
535 			flush();
536 			lc = cc = 0;
537 			while ((t = getc(fbuf)) != EOF) {
538 				if (t == '\n')
539 					lc++;
540 				if (putc(t, obuf) == EOF) {
541 					if (ispip) {
542 						npclose(fbuf);
543 						sigset(SIGINT, sigint);
544 					} else
545 						fclose(fbuf);
546 					goto werr;
547 				}
548 				cc++;
549 			}
550 			if (ispip) {
551 				npclose(fbuf);
552 				sigset(SIGINT, sigint);
553 			} else
554 				fclose(fbuf);
555 			printf("%ld/%ld\n", lc, cc);
556 			fflush(obuf);
557 			break;
558 			}
559 
560 		case 'w':
561 			/*
562 			 * Write the message on a file.
563 			 */
564 
565 			cp = &linebuf[2];
566 			while (any(*cp, " \t"))
567 				cp++;
568 			if (*cp == '\0') {
569 				fprintf(stderr, gettext("Write what file!?\n"));
570 				break;
571 			}
572 			if ((cp = expand(cp)) == NOSTR)
573 				break;
574 			fflush(obuf);
575 			rewind(ibuf);
576 			exwrite(cp, ibuf);
577 			break;
578 
579 		case 'm':
580 		case 'M':
581 		case 'f':
582 		case 'F':
583 			/*
584 			 * Interpolate the named messages, if we
585 			 * are in receiving mail mode.  Does the
586 			 * standard list processing garbage.
587 			 * If ~f or ~F is given, we don't shift over.
588 			 */
589 
590 			if (!rcvmode) {
591 				printf(gettext(
592 				    "No messages to send from!?!\n"));
593 				break;
594 			}
595 			cp = &linebuf[2];
596 			while (any(*cp, " \t"))
597 				cp++;
598 			if (forward(cp, obuf, c) < 0)
599 				goto werr;
600 			fflush(obuf);
601 			printf(gettext("(continue)\n"));
602 			break;
603 
604 		case '?':
605 			if ((fbuf = fopen(THELPFILE, "r")) == NULL) {
606 				printf(gettext("No help just now.\n"));
607 				break;
608 			}
609 			t = getc(fbuf);
610 			while (t != -1) {
611 				putchar(t);
612 				t = getc(fbuf);
613 			}
614 			fclose(fbuf);
615 			break;
616 
617 		case 'p': {
618 			/*
619 			 * Print out the current state of the
620 			 * message without altering anything.
621 			 */
622 			int nlines;
623 			extern jmp_buf pipestop;
624 			extern void brokpipe(int);
625 
626 			fflush(obuf);
627 			rewind(ibuf);
628 			fbuf = stdout;
629 			if (setjmp(pipestop))
630 				goto ret0;
631 			if (intty && outtty && (cp = value("crt")) != NOSTR) {
632 				nlines =
633 				    (*cp == '\0' ? screensize() : atoi(cp)) - 7;
634 				    /* 7 for hdr lines */
635 				while ((t = getc(ibuf)) != EOF) {
636 					if (t == '\n')
637 						if (--nlines <= 0)
638 							break;
639 				}
640 				rewind(ibuf);
641 				if (nlines <= 0) {
642 					fbuf = npopen(MORE, "w");
643 					if (fbuf == NULL) {
644 						perror(MORE);
645 						fbuf = stdout;
646 					} else {
647 						sigint = sigset(SIGINT, SIG_IGN);
648 						sigpipe = sigset(SIGPIPE, brokpipe);
649 					}
650 				}
651 			}
652 			fprintf(fbuf, gettext("-------\nMessage contains:\n"));
653 			puthead(hp, fbuf, GMASK, 0);
654 			while ((t = getc(ibuf))!=EOF)
655 				putc(t, fbuf);
656 		ret0:
657 			if (fbuf != stdout) {
658 				npclose(fbuf);
659 				sigset(SIGPIPE, sigpipe);
660 				sigset(SIGINT, sigint);
661 			}
662 			printf(gettext("(continue)\n"));
663 			break;
664 		}
665 
666 		case '^':
667 		case '|':
668 			/*
669 			 * Pipe message through command.
670 			 * Collect output as new message.
671 			 */
672 
673 			obuf = mespipe(ibuf, obuf, &linebuf[2]);
674 			newo = obuf;
675 			ibuf = newi;
676 			newi = ibuf;
677 			printf(gettext("(continue)\n"));
678 			break;
679 
680 		case 'v':
681 		case 'e':
682 			/*
683 			 * Edit the current message.
684 			 * 'e' means to use EDITOR
685 			 * 'v' means to use VISUAL
686 			 */
687 
688 			if ((obuf = mesedit(ibuf, obuf, c, hp)) == NULL)
689 				goto err;
690 			newo = obuf;
691 			ibuf = newi;
692 			printf(gettext("(continue)\n"));
693 			break;
694 		}
695 		fflush(obuf);
696 	}
697 eofl:
698 	fflush(obuf);
699 	if ((cp = value("MAILX_TAIL")) != NOSTR) {
700 	      cpout( cp, obuf);
701 	      if (isatty(fileno(stdin)))
702 		    cpout( cp, stdout);
703 	}
704 	fclose(obuf);
705 	rewind(ibuf);
706 	resetsigs(0);
707 	noreset = 0;
708 	return(ibuf);
709 
710 werr:
711 	/*
712 	 * Write error occurred on tmp file, save partial
713 	 * message in dead.letter.
714 	 */
715 	perror(tempMail);
716 	fflush(obuf);
717 	rewind(ibuf);
718 	if (fsize(ibuf) > 0) {
719 		char *deadletter;
720 
721 		deadletter = Getf("DEAD");
722 		fprintf(stderr, gettext("Saving partial message in %s\n"),
723 		    deadletter);
724 		if ((fbuf = fopen(deadletter,
725 		    value("appenddeadletter") == NOSTR ? "w" : "a")) != NULL) {
726 			chmod(deadletter, DEADPERM);
727 			puthead(hp, fbuf, GMASK|GCLEN, fsize(ibuf));
728 			lcwrite(deadletter, ibuf, fbuf, value("appenddeadletter") != NOSTR);
729 			fclose(fbuf);
730 		} else
731 			perror(deadletter);
732 	}
733 
734 err:
735 	if (ibuf != NULL)
736 		fclose(ibuf);
737 	if (obuf != NULL)
738 		fclose(obuf);
739 	resetsigs(0);
740 	noreset = 0;
741 	return(NULL);
742 }
743 
744 static void
745 resetsigs(int resethup)
746 {
747 	(void) sigset(SIGINT, savesig);
748 	if (resethup)
749 		(void) sigset(SIGHUP, savehup);
750 #ifdef SIGCONT
751 # ifdef preSVr4
752 	(void) sigset(SIGCONT, savecont);
753 # else
754 	{
755 	struct sigaction nsig;
756 	nsig.sa_handler = (void (*)())savecont;
757 	sigemptyset(&nsig.sa_mask);
758 	nsig.sa_flags = SA_RESTART;
759 	(void) sigaction(SIGCONT, &nsig, (struct sigaction*)0);
760 	}
761 # endif
762 #endif
763 }
764 
765 /*
766  * Write a file ex-like.
767  */
768 
769 static int
770 exwrite(char name[], FILE *ibuf)
771 {
772 	FILE *of;
773 	struct stat junk;
774 	void (*sigint)(int), (*sigpipe)(int);
775 	int pi = (*name == '!');
776 
777 	if ((of = pi ? npopen(++name, "w") : fopen(name, "a")) == NULL) {
778 		perror(name);
779 		return(-1);
780 	}
781 	if (pi) {
782 		sigint = sigset(SIGINT, SIG_IGN);
783 		sigpipe = sigset(SIGPIPE, SIG_IGN);
784 	}
785 	lcwrite(name, ibuf, of, 0);
786 	pi ? npclose(of) : fclose(of);
787 	if (pi) {
788 		sigset(SIGPIPE, sigpipe);
789 		sigset(SIGINT, sigint);
790 	}
791 	return(0);
792 }
793 
794 void
795 lcwrite(char *fn, FILE *fi, FILE *fo, int addnl)
796 {
797 	int c;
798 	long lc, cc;
799 
800 	printf("\"%s\" ", fn);
801 	fflush(stdout);
802 	lc = cc = 0;
803 	while ((c = getc(fi)) != EOF) {
804 		cc++;
805 		if (putc(c, fo) == '\n')
806 			lc++;
807 		if (ferror(fo)) {
808 			perror("");
809 			return;
810 		}
811 	}
812 	if (addnl) {
813 		putc('\n', fo);
814 		lc++;
815 		cc++;
816 	}
817 	fflush(fo);
818 	if (fferror(fo)) {
819 		perror("");
820 		return;
821 	}
822 	printf("%ld/%ld\n", lc, cc);
823 	fflush(stdout);
824 }
825 
826 /*
827  * Edit the message being collected on ibuf and obuf.
828  * Write the message out onto some poorly-named temp file
829  * and point an editor at it.
830  *
831  * On return, make the edit file the new temp file.
832  */
833 
834 static FILE *
835 mesedit(FILE *ibuf, FILE *obuf, int c, struct header *hp)
836 {
837 	pid_t pid;
838 	FILE *fbuf;
839 	int t;
840 	void (*sigint)(int);
841 #ifdef SIGCONT
842 	void (*sigcont)(int);
843 #endif
844 	struct stat sbuf;
845 	char *edit;
846 	char hdr[LINESIZE];
847 	char *oto, *osubject, *occ, *obcc, **oothers;
848 	int fd = -1;
849 
850 	if (stat(tempEdit, &sbuf) >= 0) {
851 		printf(gettext("%s: file exists\n"), tempEdit);
852 		goto out;
853 	}
854 	if ((fd = open(tempEdit, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
855 	(fbuf = fdopen(fd, "w")) == NULL) {
856 		perror(tempEdit);
857 		goto out;
858 	}
859 	fflush(obuf);
860 	rewind(ibuf);
861 	puthead(hp, fbuf, GMASK, 0);
862 	while ((t = getc(ibuf)) != EOF)
863 		putc(t, fbuf);
864 	fflush(fbuf);
865 	if (fferror(fbuf)) {
866 		perror(tempEdit);
867 		removefile(tempEdit);
868 		goto out;
869 	}
870 	fclose(fbuf);
871 	if ((edit = value(c == 'e' ? "EDITOR" : "VISUAL")) == NOSTR ||
872 	    *edit == '\0')
873 		edit = c == 'e' ? EDITOR : VISUAL;
874 	edit = safeexpand(edit);
875 
876 	/*
877 	 * Fork/execlp the editor on the edit file
878 	*/
879 
880 	pid = vfork();
881 	if (pid == (pid_t)-1) {
882 		perror("fork");
883 		removefile(tempEdit);
884 		goto out;
885 	}
886 	if (pid == 0) {
887 		char ecmd[BUFSIZ];
888 		char *Shell;
889 
890 		sigchild();
891 		execlp(edit, edit, tempEdit, (char *)0);
892 		/*
893 		 * If execlp fails, "edit" might really be a complete
894 		 * shell command, not a simple pathname.  Try using
895 		 * the shell to run it.
896 		 */
897 		snprintf(ecmd, sizeof (ecmd), "exec %s %s", edit, tempEdit);
898 		if ((Shell = value("SHELL")) == NULL || *Shell=='\0')
899 			Shell = SHELL;
900 		execlp(Shell, Shell, "-c", ecmd, NULL);
901 		perror(edit);
902 		_exit(1);
903 	}
904 	sigint = sigset(SIGINT, SIG_IGN);
905 #ifdef SIGCONT
906 	sigcont = sigset(SIGCONT, SIG_DFL);
907 #endif
908 	while (wait((int *)0) != pid)
909 		;
910 	sigset(SIGINT, sigint);
911 #ifdef SIGCONT
912 	sigset(SIGCONT, sigcont);
913 #endif
914 	/*
915 	 * Now switch to new file.
916 	 */
917 
918 	if ((fbuf = fopen(tempEdit, "r")) == NULL) {
919 		perror(tempEdit);
920 		removefile(tempEdit);
921 		goto out;
922 	}
923 	removefile(tempEdit);
924 
925 	/* save the old headers, in case they are accidentally deleted */
926 	osubject = hp->h_subject;
927 	oto = hp->h_to;
928 	occ = hp->h_cc;
929 	obcc = hp->h_bcc;
930 	oothers = hp->h_others;
931 	hp->h_to = hp->h_subject = hp->h_cc = hp->h_bcc = hp->h_defopt = NOSTR;
932 	hp->h_others = NOSTRPTR;
933 	hp->h_seq = 0;
934 	while (gethfield(fbuf, hdr, 9999L) > 0) {
935 		if (ishfield(hdr, "to"))
936 			hp->h_to = addto(hp->h_to, hcontents(hdr));
937 		else if (ishfield(hdr, "subject"))
938 			hp->h_subject = addone(hp->h_subject, hcontents(hdr));
939 		else if (ishfield(hdr, "cc"))
940 			hp->h_cc = addto(hp->h_cc, hcontents(hdr));
941 		else if (ishfield(hdr, "bcc"))
942 			hp->h_bcc = addto(hp->h_bcc, hcontents(hdr));
943 		else if (ishfield(hdr, "default-options"))
944 			hp->h_defopt = addone(hp->h_defopt, hcontents(hdr));
945 		else
946 			hp->h_others = Xaddone(hp->h_others, hdr);
947 		hp->h_seq++;
948 	}
949 	if (hp->h_seq == 0) {
950 		/* if we didn't see any headers, restore the original headers */
951 		hp->h_subject = osubject;
952 		hp->h_to = oto;
953 		hp->h_cc = occ;
954 		hp->h_bcc = obcc;
955 		hp->h_others = oothers;
956 		printf(gettext(
957 		    "(Deleted headers restored to original values)\n"));
958 	}
959 	if ((fd = open(tempMail, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
960 	(obuf = fdopen(fd, "w")) == NULL) {
961 		perror(tempMail);
962 		fclose(fbuf);
963 		goto out;
964 	}
965 	if ((ibuf = fopen(tempMail, "r")) == NULL) {
966 		perror(tempMail);
967 		removefile(tempMail);
968 		fclose(fbuf);
969 		fclose(obuf);
970 		goto out;
971 	}
972 	removefile(tempMail);
973 	if (strlen(hdr) != 0) {
974 		fputs(hdr, obuf);
975 		putc('\n', obuf);
976 	}
977 	while ((t = getc(fbuf)) != EOF)
978 		putc(t, obuf);
979 	fclose(fbuf);
980 	fclose(newo);
981 	fclose(newi);
982 	newo = obuf;
983 	newi = ibuf;
984 out:
985 	return(newo);
986 }
987 
988 /*
989  * Pipe the message through the command.
990  * Old message is on stdin of command;
991  * New message collected from stdout.
992  * Sh -c must return 0 to accept the new message.
993  */
994 
995 static FILE *
996 mespipe(FILE *ibuf, FILE *obuf, char cmd[])
997 {
998 	FILE *ni, *no;
999 	pid_t pid;
1000 	int s;
1001 	void (*sigint)(int);
1002 	char *Shell;
1003 	int fd = -1;
1004 
1005 	newi = ibuf;
1006 	if ((fd = open(tempEdit, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0 ||
1007 	(no = fdopen(fd, "w")) == NULL) {
1008 		perror(tempEdit);
1009 		return(obuf);
1010 	}
1011 	if ((ni = fopen(tempEdit, "r")) == NULL) {
1012 		perror(tempEdit);
1013 		fclose(no);
1014 		removefile(tempEdit);
1015 		return(obuf);
1016 	}
1017 	removefile(tempEdit);
1018 	fflush(obuf);
1019 	rewind(ibuf);
1020 	if ((Shell = value("SHELL")) == NULL || *Shell=='\0')
1021 		Shell = SHELL;
1022 	if ((pid = vfork()) == (pid_t)-1) {
1023 		perror("fork");
1024 		goto err;
1025 	}
1026 	if (pid == 0) {
1027 		/*
1028 		 * stdin = current message.
1029 		 * stdout = new message.
1030 		 */
1031 
1032 		sigchild();
1033 		close(0);
1034 		dup(fileno(ibuf));
1035 		close(1);
1036 		dup(fileno(no));
1037 		for (s = 4; s < 15; s++)
1038 			close(s);
1039 		execlp(Shell, Shell, "-c", cmd, (char *)0);
1040 		perror(Shell);
1041 		_exit(1);
1042 	}
1043 	sigint = sigset(SIGINT, SIG_IGN);
1044 	while (wait(&s) != pid)
1045 		;
1046 	sigset(SIGINT, sigint);
1047 	if (s != 0 || pid == (pid_t)-1) {
1048 		fprintf(stderr, gettext("\"%s\" failed!?\n"), cmd);
1049 		goto err;
1050 	}
1051 	if (fsize(ni) == 0) {
1052 		fprintf(stderr, gettext("No bytes from \"%s\" !?\n"), cmd);
1053 		goto err;
1054 	}
1055 
1056 	/*
1057 	 * Take new files.
1058 	 */
1059 
1060 	newi = ni;
1061 	fclose(ibuf);
1062 	fclose(obuf);
1063 	return(no);
1064 
1065 err:
1066 	fclose(no);
1067 	fclose(ni);
1068 	return(obuf);
1069 }
1070 
1071 static char *indentprefix;	/* used instead of tab by tabputs */
1072 
1073 /*
1074  * Interpolate the named messages into the current
1075  * message, preceding each line with a tab.
1076  * Return a count of the number of characters now in
1077  * the message, or -1 if an error is encountered writing
1078  * the message temporary.  The flag argument is 'm' if we
1079  * should shift over and 'f' if not.
1080  */
1081 static int
1082 forward(char ms[], FILE *obuf, int f)
1083 {
1084 	int *msgvec, *ip;
1085 
1086 	msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
1087 	if (msgvec == NOINTPTR)
1088 		return(0);
1089 	if (getmsglist(ms, msgvec, 0) < 0)
1090 		return(0);
1091 	if (*msgvec == 0) {
1092 		*msgvec = first(0, MMNORM);
1093 		if (*msgvec == 0) {
1094 			printf(gettext("No appropriate messages\n"));
1095 			return(0);
1096 		}
1097 		msgvec[1] = 0;
1098 	}
1099 	if (tolower(f) == 'm')
1100 		indentprefix = value("indentprefix");
1101 	printf(gettext("Interpolating:"));
1102 	for (ip = msgvec; *ip != 0; ip++) {
1103 		touch(*ip);
1104 		printf(" %d", *ip);
1105 		if (msend(&message[*ip-1], obuf, islower(f) ? M_IGNORE : 0,
1106 		    tolower(f) == 'm' ? tabputs : fputs) < 0) {
1107 			perror(tempMail);
1108 			return(-1);
1109 		}
1110 	}
1111 	fflush(obuf);
1112 	if (fferror(obuf)) {
1113 		perror(tempMail);
1114 		return(-1);
1115 	}
1116 	printf("\n");
1117 	return(0);
1118 }
1119 
1120 static int
1121 tabputs(const char *line, FILE *obuf)
1122 {
1123 
1124 	if (indentprefix)
1125 		fputs(indentprefix, obuf);
1126 	/* Don't create lines with only a tab on them */
1127 	else if (line[0] != '\n')
1128 		fputc('\t', obuf);
1129 	return (fputs(line, obuf));
1130 }
1131 
1132 /*
1133  * Print (continue) when continued after ^Z.
1134  */
1135 #ifdef SIGCONT
1136 static void
1137 #ifdef	__cplusplus
1138 collcont(int)
1139 #else
1140 /* ARGSUSED */
1141 collcont(int s)
1142 #endif
1143 {
1144 	printf(gettext("(continue)\n"));
1145 	fflush(stdout);
1146 }
1147 #endif /* SIGCONT */
1148 
1149 /*
1150  * On interrupt, go here to save the partial
1151  * message on ~/dead.letter.
1152  * Then restore signals and execute the normal
1153  * signal routine.  We only come here if signals
1154  * were previously set anyway.
1155  */
1156 static void
1157 collrub(int s)
1158 {
1159 	FILE *dbuf;
1160 	char *deadletter;
1161 
1162 # ifdef OLD_BSD_SIGS
1163 	if (s == SIGHUP)
1164 		sigignore(SIGHUP);
1165 # endif
1166 	if (s == SIGINT && hadintr == 0) {
1167 		hadintr++;
1168 		fflush(stdout);
1169 		fprintf(stderr,
1170 		    gettext("\n(Interrupt -- one more to kill letter)\n"));
1171 # ifdef OLD_BSD_SIGS
1172 		sigrelse(s);
1173 # endif
1174 		longjmp(coljmp, 1);
1175 	}
1176 	fclose(newo);
1177 	rewind(newi);
1178 	if (s == SIGINT && value("save")==NOSTR || fsize(newi) == 0)
1179 		goto done;
1180 	deadletter = Getf("DEAD");
1181 	if ((dbuf = fopen(deadletter,
1182 	    (value("appenddeadletter") == NOSTR ? "w" : "a"))) == NULL) {
1183 		perror(deadletter);
1184 		goto done;
1185 	}
1186 	chmod(deadletter, DEADPERM);
1187 	puthead(savehp, dbuf, GMASK|GCLEN, fsize(newi));
1188 	lcwrite(deadletter, newi, dbuf, value("appenddeadletter") != NOSTR);
1189 	fclose(dbuf);
1190 done:
1191 	fclose(newi);
1192 	resetsigs(1);
1193 	if (rcvmode) {
1194 		if (s == SIGHUP)
1195 			hangup(s);
1196 		else
1197 			stop(s);
1198 	}
1199 	else
1200 		exit(1);
1201 }
1202 
1203 /*
1204  * Acknowledge an interrupt signal from the tty by typing an @
1205  */
1206 static void
1207 #ifdef	__cplusplus
1208 intack(int)
1209 #else
1210 /* ARGSUSED */
1211 intack(int s)
1212 #endif
1213 {
1214 
1215 	puts("@");
1216 	fflush(stdout);
1217 	clearerr(stdin);
1218 	longjmp(coljmp,1);
1219 }
1220 
1221 /* Read line from stdin, noting any NULL characters.
1222    Return the number of characters read. Note that the buffer
1223    passed must be 1 larger than "size" for the trailing NUL byte.
1224  */
1225 int
1226 getaline(char *line, int size, FILE *f, int *hasnulls)
1227 {
1228 	int i, ch;
1229 	for (i = 0; (i < size) && ((ch=getc(f)) != EOF); ) {
1230 		if ( ch == '\0' )
1231 			*hasnulls = 1;
1232 		if ((line[i++] = (char)ch) == '\n') break;
1233 	}
1234 	line[i] = '\0';
1235 	return(i);
1236 }
1237 
1238 void
1239 #ifdef	__cplusplus
1240 savedead(int)
1241 #else
1242 /* ARGSUSED */
1243 savedead(int s)
1244 #endif
1245 {
1246 	collrub(SIGINT);
1247 	exit(1);
1248 	/* NOTREACHED */
1249 }
1250 
1251 /*
1252  * Add a list of addresses to the end of a header entry field.
1253  */
1254 char *
1255 addto(char hf[], char news[])
1256 {
1257 	char name[LINESIZE];
1258 	int comma = docomma(news);
1259 
1260 	while (news = yankword(news, name, sizeof (name), comma)) {
1261 		nstrcat(name, sizeof (name), ", ");
1262 		hf = addone(hf, name);
1263 	}
1264 	return hf;
1265 }
1266 
1267 /*
1268  * Add a string to the end of a header entry field.
1269  */
1270 char *
1271 addone(char hf[], char news[])
1272 {
1273 	char *cp, *cp2, *linebuf;
1274 
1275 	if (hf == NOSTR)
1276 		hf = savestr("");
1277 	if (*news == '\0')
1278 		return(hf);
1279 	linebuf = (char *)srealloc(hf, (unsigned)(strlen(hf) + strlen(news) + 2));
1280 	cp2 = strchr(linebuf, '\0');
1281 	if (cp2 > linebuf && cp2[-1] != ' ')
1282 		*cp2++ = ' ';
1283 	for (cp = news; any(*cp, " \t"); cp++)
1284 		;
1285 	while (*cp != '\0')
1286 		*cp2++ = *cp++;
1287 	*cp2 = '\0';
1288 	return(linebuf);
1289 }
1290 
1291 static int
1292 nptrs(char **hf)
1293 {
1294 	int i;
1295 
1296 	if (!hf)
1297 		return(0);
1298 	for (i = 0; *hf; hf++)
1299 		i++;
1300 	return(i);
1301 }
1302 
1303 /*
1304  * Add a non-standard header to the end of the non-standard headers.
1305  */
1306 static char **
1307 Xaddone(char **hf, char news[])
1308 {
1309 	char *linebuf;
1310 	char **ohf = hf;
1311 	int nhf = nptrs(hf);
1312 
1313 	if (hf == NOSTRPTR)
1314 		hf = (char**)salloc(sizeof(char*) * 2);
1315 	else
1316 		hf = (char**)srealloc(hf, sizeof(char*) * (nhf + 2));
1317 	if (hf == NOSTRPTR) {
1318 		fprintf(stderr, gettext("No room, header lost: %s\n"), news);
1319 		return(ohf);
1320 	}
1321 	linebuf = (char *)salloc((unsigned)(strlen(news) + 1));
1322 	strcpy(linebuf, news);
1323 	hf[nhf++] = linebuf;
1324 	hf[nhf] = NOSTR;
1325 	return(hf);
1326 }
1327 
1328 static void
1329 cpout(char *str, FILE *ofd)
1330 {
1331 	char *cp = str;
1332 
1333 	while (*cp) {
1334 		if (*cp == '\\') {
1335 			switch (*(cp+1)) {
1336 			case 'n':
1337 				putc('\n', ofd);
1338 				cp++;
1339 				break;
1340 			case 't':
1341 				putc('\t', ofd);
1342 				cp++;
1343 				break;
1344 			default:
1345 				putc('\\', ofd);
1346 			}
1347 		} else {
1348 			putc(*cp, ofd);
1349 		}
1350 		cp++;
1351 	}
1352 	putc('\n', ofd);
1353 	fflush(ofd);
1354 }
1355 
1356 static void
1357 xhalt(void)
1358 {
1359 	fclose(newo);
1360 	fclose(newi);
1361 	sigset(SIGINT, savesig);
1362 	sigset(SIGHUP, savehup);
1363 	if (rcvmode)
1364 		stop(0);
1365 	exit(1);
1366 	/* NOTREACHED */
1367 }
1368 
1369 /*
1370  * Strip the nulls from a buffer of length n
1371  */
1372 static int
1373 stripnulls(char *linebuf, int nread)
1374 {
1375 	int i, j;
1376 
1377 	for (i = 0; i < nread; i++)
1378 		if (linebuf[i] == '\0')
1379 			break;
1380 	for (j = i; j < nread; j++)
1381 		if (linebuf[j] != '\0')
1382 			linebuf[i++] = linebuf[j];
1383 	linebuf[i] = '\0';
1384 	return(i);
1385 }
1386