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
51static void	collcont(int);
52#endif
53static void	collrub(int s);
54static void	cpout(char *str, FILE *ofd);
55static int	exwrite(char name[], FILE *ibuf);
56static int	forward(char ms[], FILE *obuf, int f);
57static void	intack(int);
58static int	forward(char ms[], FILE *obuf, int f);
59static FILE	*mesedit(FILE *ibuf, FILE *obuf, int c, struct header *hp);
60static FILE	*mespipe(FILE *ibuf, FILE *obuf, char cmd[]);
61static void	resetsigs(int resethup);
62static int	stripnulls(char *linebuf, int nread);
63static void	xhalt(void);
64static char	**Xaddone(char **hf, char news[]);
65static 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
80static void		(*savesig)(int);	/* Previous SIGINT value */
81static void		(*savehup)(int);	/* Previous SIGHUP value */
82#ifdef SIGCONT
83static void		(*savecont)(int);	/* Previous SIGCONT value */
84#endif
85static FILE		*newi;		/* File for saving away */
86static FILE		*newo;		/* Output side of same */
87static int		ignintr;	/* Ignore interrups */
88static int		hadintr;	/* Have seen one SIGINT so far */
89static struct header	*savehp;
90static jmp_buf		coljmp;		/* To get back to work */
91
92FILE *
93collect(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	}
697eofl:
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
710werr:
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
734err:
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
744static void
745resetsigs(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
769static int
770exwrite(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
794void
795lcwrite(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
834static FILE *
835mesedit(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;
984out:
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
995static FILE *
996mespipe(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
1065err:
1066	fclose(no);
1067	fclose(ni);
1068	return(obuf);
1069}
1070
1071static 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 */
1081static int
1082forward(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
1120static int
1121tabputs(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
1136static void
1137#ifdef	__cplusplus
1138collcont(int)
1139#else
1140/* ARGSUSED */
1141collcont(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 */
1156static void
1157collrub(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);
1190done:
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 */
1206static void
1207#ifdef	__cplusplus
1208intack(int)
1209#else
1210/* ARGSUSED */
1211intack(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 */
1225int
1226getaline(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
1238void
1239#ifdef	__cplusplus
1240savedead(int)
1241#else
1242/* ARGSUSED */
1243savedead(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 */
1254char *
1255addto(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 */
1270char *
1271addone(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
1291static int
1292nptrs(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 */
1306static char **
1307Xaddone(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
1328static void
1329cpout(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
1356static void
1357xhalt(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 */
1372static int
1373stripnulls(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