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