1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * Mail -- a mail program
34 *
35 * Collect input from standard input, handling
36 * ~ escapes.
37 */
38
39#include "rcv.h"
40#include <fcntl.h>
41#include "extern.h"
42
43/*
44 * Read a message from standard input and return a read file to it
45 * or NULL on error.
46 */
47
48/*
49 * The following hokiness with global variables is so that on
50 * receipt of an interrupt signal, the partial message can be salted
51 * away on dead.letter.
52 */
53
54static	sig_t	saveint;		/* Previous SIGINT value */
55static	sig_t	savehup;		/* Previous SIGHUP value */
56static	sig_t	savetstp;		/* Previous SIGTSTP value */
57static	sig_t	savettou;		/* Previous SIGTTOU value */
58static	sig_t	savettin;		/* Previous SIGTTIN value */
59static	FILE	*collf;			/* File for saving away */
60static	int	hadintr;		/* Have seen one SIGINT so far */
61
62static	jmp_buf	colljmp;		/* To get back to work */
63static	int	colljmp_p;		/* whether to long jump */
64static	jmp_buf	collabort;		/* To end collection with error */
65
66FILE *
67collect(struct header *hp, int printheaders)
68{
69	FILE *fbuf;
70	int lc, cc, escape, eofcount, fd, c, t;
71	char linebuf[LINESIZE], tempname[PATHSIZE], *cp, getsub;
72	sigset_t nset;
73	int longline, lastlong, rc;	/* So we don't make 2 or more lines
74					   out of a long input line. */
75
76	collf = NULL;
77	/*
78	 * Start catching signals from here, but we're still die on interrupts
79	 * until we're in the main loop.
80	 */
81	(void)sigemptyset(&nset);
82	(void)sigaddset(&nset, SIGINT);
83	(void)sigaddset(&nset, SIGHUP);
84	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
85	if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
86		(void)signal(SIGINT, collint);
87	if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
88		(void)signal(SIGHUP, collhup);
89	savetstp = signal(SIGTSTP, collstop);
90	savettou = signal(SIGTTOU, collstop);
91	savettin = signal(SIGTTIN, collstop);
92	if (setjmp(collabort) || setjmp(colljmp)) {
93		(void)rm(tempname);
94		goto err;
95	}
96	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
97
98	noreset++;
99	(void)snprintf(tempname, sizeof(tempname),
100	    "%s/mail.RsXXXXXXXXXX", tmpdir);
101	if ((fd = mkstemp(tempname)) == -1 ||
102	    (collf = Fdopen(fd, "w+")) == NULL) {
103		warn("%s", tempname);
104		goto err;
105	}
106	(void)rm(tempname);
107
108	/*
109	 * If we are going to prompt for a subject,
110	 * refrain from printing a newline after
111	 * the headers (since some people mind).
112	 */
113	t = GTO|GSUBJECT|GCC|GNL;
114	getsub = 0;
115	if (hp->h_subject == NULL && value("interactive") != NULL &&
116	    (value("ask") != NULL || value("asksub") != NULL))
117		t &= ~GNL, getsub++;
118	if (printheaders) {
119		puthead(hp, stdout, t);
120		(void)fflush(stdout);
121	}
122	if ((cp = value("escape")) != NULL)
123		escape = *cp;
124	else
125		escape = ESCAPE;
126	eofcount = 0;
127	hadintr = 0;
128	longline = 0;
129
130	if (!setjmp(colljmp)) {
131		if (getsub)
132			grabh(hp, GSUBJECT);
133	} else {
134		/*
135		 * Come here for printing the after-signal message.
136		 * Duplicate messages won't be printed because
137		 * the write is aborted if we get a SIGTTOU.
138		 */
139cont:
140		if (hadintr) {
141			(void)fflush(stdout);
142			fprintf(stderr,
143			"\n(Interrupt -- one more to kill letter)\n");
144		} else {
145			printf("(continue)\n");
146			(void)fflush(stdout);
147		}
148	}
149	for (;;) {
150		colljmp_p = 1;
151		c = readline(stdin, linebuf, LINESIZE);
152		colljmp_p = 0;
153		if (c < 0) {
154			if (value("interactive") != NULL &&
155			    value("ignoreeof") != NULL && ++eofcount < 25) {
156				printf("Use \".\" to terminate letter\n");
157				continue;
158			}
159			break;
160		}
161		lastlong = longline;
162		longline = c == LINESIZE - 1;
163		eofcount = 0;
164		hadintr = 0;
165		if (linebuf[0] == '.' && linebuf[1] == '\0' &&
166		    value("interactive") != NULL && !lastlong &&
167		    (value("dot") != NULL || value("ignoreeof") != NULL))
168			break;
169		if (linebuf[0] != escape || value("interactive") == NULL ||
170		    lastlong) {
171			if (putline(collf, linebuf, !longline) < 0)
172				goto err;
173			continue;
174		}
175		c = linebuf[1];
176		switch (c) {
177		default:
178			/*
179			 * On double escape, just send the single one.
180			 * Otherwise, it's an error.
181			 */
182			if (c == escape) {
183				if (putline(collf, &linebuf[1], !longline) < 0)
184					goto err;
185				else
186					break;
187			}
188			printf("Unknown tilde escape.\n");
189			break;
190		case 'C':
191			/*
192			 * Dump core.
193			 */
194			core(NULL);
195			break;
196		case '!':
197			/*
198			 * Shell escape, send the balance of the
199			 * line to sh -c.
200			 */
201			shell(&linebuf[2]);
202			break;
203		case ':':
204		case '_':
205			/*
206			 * Escape to command mode, but be nice!
207			 */
208			execute(&linebuf[2], 1);
209			goto cont;
210		case '.':
211			/*
212			 * Simulate end of file on input.
213			 */
214			goto out;
215		case 'q':
216			/*
217			 * Force a quit of sending mail.
218			 * Act like an interrupt happened.
219			 */
220			hadintr++;
221			collint(SIGINT);
222			exit(1);
223		case 'x':
224			/*
225			 * Exit, do not save in dead.letter.
226			 */
227			goto err;
228		case 'h':
229			/*
230			 * Grab a bunch of headers.
231			 */
232			grabh(hp, GTO|GSUBJECT|GCC|GBCC);
233			goto cont;
234		case 't':
235			/*
236			 * Add to the To list.
237			 */
238			hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
239			break;
240		case 's':
241			/*
242			 * Set the Subject line.
243			 */
244			cp = &linebuf[2];
245			while (isspace((unsigned char)*cp))
246				cp++;
247			hp->h_subject = savestr(cp);
248			break;
249		case 'R':
250			/*
251			 * Set the Reply-To line.
252			 */
253			cp = &linebuf[2];
254			while (isspace((unsigned char)*cp))
255				cp++;
256			hp->h_replyto = savestr(cp);
257			break;
258		case 'c':
259			/*
260			 * Add to the CC list.
261			 */
262			hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
263			break;
264		case 'b':
265			/*
266			 * Add to the BCC list.
267			 */
268			hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
269			break;
270		case 'i':
271		case 'A':
272		case 'a':
273			/*
274			 * Insert named variable in message.
275			 */
276			switch(c) {
277				case 'i':
278					cp = &linebuf[2];
279					while(isspace((unsigned char)*cp))
280						cp++;
281					break;
282				case 'a':
283					cp = "sign";
284					break;
285				case 'A':
286					cp = "Sign";
287					break;
288				default:
289					goto err;
290			}
291
292			if(*cp != '\0' && (cp = value(cp)) != NULL) {
293				printf("%s\n", cp);
294				if(putline(collf, cp, 1) < 0)
295					goto err;
296			}
297
298			break;
299		case 'd':
300			/*
301			 * Read in the dead letter file.
302			 */
303			if (strlcpy(linebuf + 2, getdeadletter(),
304				sizeof(linebuf) - 2)
305			    >= sizeof(linebuf) - 2) {
306				printf("Line buffer overflow\n");
307				break;
308			}
309			/* FALLTHROUGH */
310		case 'r':
311		case '<':
312			/*
313			 * Invoke a file:
314			 * Search for the file name,
315			 * then open it and copy the contents to collf.
316			 */
317			cp = &linebuf[2];
318			while (isspace((unsigned char)*cp))
319				cp++;
320			if (*cp == '\0') {
321				printf("Interpolate what file?\n");
322				break;
323			}
324			cp = expand(cp);
325			if (cp == NULL)
326				break;
327			if (*cp == '!') {
328				/*
329				 * Insert stdout of command.
330				 */
331				char *sh;
332				int nullfd, tempfd, rc;
333				char tempname2[PATHSIZE];
334
335				if ((nullfd = open(_PATH_DEVNULL, O_RDONLY, 0))
336				    == -1) {
337					warn(_PATH_DEVNULL);
338					break;
339				}
340
341				(void)snprintf(tempname2, sizeof(tempname2),
342				    "%s/mail.ReXXXXXXXXXX", tmpdir);
343				if ((tempfd = mkstemp(tempname2)) == -1 ||
344				    (fbuf = Fdopen(tempfd, "w+")) == NULL) {
345					warn("%s", tempname2);
346					break;
347				}
348				(void)unlink(tempname2);
349
350				if ((sh = value("SHELL")) == NULL)
351					sh = _PATH_CSHELL;
352
353				rc = run_command(sh, 0, nullfd, fileno(fbuf),
354				    "-c", cp+1, NULL);
355
356				close(nullfd);
357
358				if (rc < 0) {
359					(void)Fclose(fbuf);
360					break;
361				}
362
363				if (fsize(fbuf) == 0) {
364					fprintf(stderr,
365					    "No bytes from command \"%s\"\n",
366					    cp+1);
367					(void)Fclose(fbuf);
368					break;
369				}
370
371				rewind(fbuf);
372			} else if (isdir(cp)) {
373				printf("%s: Directory\n", cp);
374				break;
375			} else if ((fbuf = Fopen(cp, "r")) == NULL) {
376				warn("%s", cp);
377				break;
378			}
379			printf("\"%s\" ", cp);
380			(void)fflush(stdout);
381			lc = 0;
382			cc = 0;
383			while ((rc = readline(fbuf, linebuf, LINESIZE)) >= 0) {
384				if (rc != LINESIZE - 1)
385					lc++;
386				if ((t = putline(collf, linebuf,
387					 rc != LINESIZE - 1)) < 0) {
388					(void)Fclose(fbuf);
389					goto err;
390				}
391				cc += t;
392			}
393			(void)Fclose(fbuf);
394			printf("%d/%d\n", lc, cc);
395			break;
396		case 'w':
397			/*
398			 * Write the message on a file.
399			 */
400			cp = &linebuf[2];
401			while (*cp == ' ' || *cp == '\t')
402				cp++;
403			if (*cp == '\0') {
404				fprintf(stderr, "Write what file!?\n");
405				break;
406			}
407			if ((cp = expand(cp)) == NULL)
408				break;
409			rewind(collf);
410			exwrite(cp, collf, 1);
411			break;
412		case 'm':
413		case 'M':
414		case 'f':
415		case 'F':
416			/*
417			 * Interpolate the named messages, if we
418			 * are in receiving mail mode.  Does the
419			 * standard list processing garbage.
420			 * If ~f is given, we don't shift over.
421			 */
422			if (forward(linebuf + 2, collf, tempname, c) < 0)
423				goto err;
424			goto cont;
425		case '?':
426			if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
427				warn("%s", _PATH_TILDE);
428				break;
429			}
430			while ((t = getc(fbuf)) != EOF)
431				(void)putchar(t);
432			(void)Fclose(fbuf);
433			break;
434		case 'p':
435			/*
436			 * Print out the current state of the
437			 * message without altering anything.
438			 */
439			rewind(collf);
440			printf("-------\nMessage contains:\n");
441			puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
442			while ((t = getc(collf)) != EOF)
443				(void)putchar(t);
444			goto cont;
445		case '|':
446			/*
447			 * Pipe message through command.
448			 * Collect output as new message.
449			 */
450			rewind(collf);
451			mespipe(collf, &linebuf[2]);
452			goto cont;
453		case 'v':
454		case 'e':
455			/*
456			 * Edit the current message.
457			 * 'e' means to use EDITOR
458			 * 'v' means to use VISUAL
459			 */
460			rewind(collf);
461			mesedit(collf, c);
462			goto cont;
463		}
464	}
465	goto out;
466err:
467	if (collf != NULL) {
468		(void)Fclose(collf);
469		collf = NULL;
470	}
471out:
472	if (collf != NULL)
473		rewind(collf);
474	noreset--;
475	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
476	(void)signal(SIGINT, saveint);
477	(void)signal(SIGHUP, savehup);
478	(void)signal(SIGTSTP, savetstp);
479	(void)signal(SIGTTOU, savettou);
480	(void)signal(SIGTTIN, savettin);
481	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
482	return (collf);
483}
484
485/*
486 * Write a file, ex-like if f set.
487 */
488int
489exwrite(char name[], FILE *fp, int f)
490{
491	FILE *of;
492	int c, lc;
493	long cc;
494	struct stat junk;
495
496	if (f) {
497		printf("\"%s\" ", name);
498		(void)fflush(stdout);
499	}
500	if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
501		if (!f)
502			fprintf(stderr, "%s: ", name);
503		fprintf(stderr, "File exists\n");
504		return (-1);
505	}
506	if ((of = Fopen(name, "w")) == NULL) {
507		warn((char *)NULL);
508		return (-1);
509	}
510	lc = 0;
511	cc = 0;
512	while ((c = getc(fp)) != EOF) {
513		cc++;
514		if (c == '\n')
515			lc++;
516		(void)putc(c, of);
517		if (ferror(of)) {
518			warnx("%s", name);
519			(void)Fclose(of);
520			return (-1);
521		}
522	}
523	(void)Fclose(of);
524	printf("%d/%ld\n", lc, cc);
525	(void)fflush(stdout);
526	return (0);
527}
528
529/*
530 * Edit the message being collected on fp.
531 * On return, make the edit file the new temp file.
532 */
533void
534mesedit(FILE *fp, int c)
535{
536	sig_t sigint = signal(SIGINT, SIG_IGN);
537	FILE *nf = run_editor(fp, (off_t)-1, c, 0);
538
539	if (nf != NULL) {
540		(void)fseeko(nf, (off_t)0, SEEK_END);
541		collf = nf;
542		(void)Fclose(fp);
543	}
544	(void)signal(SIGINT, sigint);
545}
546
547/*
548 * Pipe the message through the command.
549 * Old message is on stdin of command;
550 * New message collected from stdout.
551 * Sh -c must return 0 to accept the new message.
552 */
553void
554mespipe(FILE *fp, char cmd[])
555{
556	FILE *nf;
557	int fd;
558	sig_t sigint = signal(SIGINT, SIG_IGN);
559	char *sh, tempname[PATHSIZE];
560
561	(void)snprintf(tempname, sizeof(tempname),
562	    "%s/mail.ReXXXXXXXXXX", tmpdir);
563	if ((fd = mkstemp(tempname)) == -1 ||
564	    (nf = Fdopen(fd, "w+")) == NULL) {
565		warn("%s", tempname);
566		goto out;
567	}
568	(void)rm(tempname);
569	/*
570	 * stdin = current message.
571	 * stdout = new message.
572	 */
573	if ((sh = value("SHELL")) == NULL)
574		sh = _PATH_CSHELL;
575	if (run_command(sh,
576	    0, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
577		(void)Fclose(nf);
578		goto out;
579	}
580	if (fsize(nf) == 0) {
581		fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
582		(void)Fclose(nf);
583		goto out;
584	}
585	/*
586	 * Take new files.
587	 */
588	(void)fseeko(nf, (off_t)0, SEEK_END);
589	collf = nf;
590	(void)Fclose(fp);
591out:
592	(void)signal(SIGINT, sigint);
593}
594
595/*
596 * Interpolate the named messages into the current
597 * message, preceding each line with a tab.
598 * Return a count of the number of characters now in
599 * the message, or -1 if an error is encountered writing
600 * the message temporary.  The flag argument is 'm' if we
601 * should shift over and 'f' if not.
602 */
603int
604forward(char ms[], FILE *fp, char *fn, int f)
605{
606	int *msgvec;
607	struct ignoretab *ig;
608	char *tabst;
609
610	msgvec = (int *)salloc((msgCount+1) * sizeof(*msgvec));
611	if (msgvec == NULL)
612		return (0);
613	if (getmsglist(ms, msgvec, 0) < 0)
614		return (0);
615	if (*msgvec == 0) {
616		*msgvec = first(0, MMNORM);
617		if (*msgvec == 0) {
618			printf("No appropriate messages\n");
619			return (0);
620		}
621		msgvec[1] = 0;
622	}
623	if (f == 'f' || f == 'F')
624		tabst = NULL;
625	else if ((tabst = value("indentprefix")) == NULL)
626		tabst = "\t";
627	ig = isupper((unsigned char)f) ? NULL : ignore;
628	printf("Interpolating:");
629	for (; *msgvec != 0; msgvec++) {
630		struct message *mp = message + *msgvec - 1;
631
632		touch(mp);
633		printf(" %d", *msgvec);
634		if (sendmessage(mp, fp, ig, tabst) < 0) {
635			warnx("%s", fn);
636			return (-1);
637		}
638	}
639	printf("\n");
640	return (0);
641}
642
643/*
644 * Print (continue) when continued after ^Z.
645 */
646/*ARGSUSED*/
647void
648collstop(int s)
649{
650	sig_t old_action = signal(s, SIG_DFL);
651	sigset_t nset;
652
653	(void)sigemptyset(&nset);
654	(void)sigaddset(&nset, s);
655	(void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
656	(void)kill(0, s);
657	(void)sigprocmask(SIG_BLOCK, &nset, NULL);
658	(void)signal(s, old_action);
659	if (colljmp_p) {
660		colljmp_p = 0;
661		hadintr = 0;
662		longjmp(colljmp, 1);
663	}
664}
665
666/*
667 * On interrupt, come here to save the partial message in ~/dead.letter.
668 * Then jump out of the collection loop.
669 */
670/*ARGSUSED*/
671void
672collint(int s __unused)
673{
674	/*
675	 * the control flow is subtle, because we can be called from ~q.
676	 */
677	if (!hadintr) {
678		if (value("ignore") != NULL) {
679			printf("@");
680			(void)fflush(stdout);
681			clearerr(stdin);
682			return;
683		}
684		hadintr = 1;
685		longjmp(colljmp, 1);
686	}
687	rewind(collf);
688	if (value("nosave") == NULL)
689		savedeadletter(collf);
690	longjmp(collabort, 1);
691}
692
693/*ARGSUSED*/
694void
695collhup(int s __unused)
696{
697	rewind(collf);
698	savedeadletter(collf);
699	/*
700	 * Let's pretend nobody else wants to clean up,
701	 * a true statement at this time.
702	 */
703	exit(1);
704}
705
706void
707savedeadletter(FILE *fp)
708{
709	FILE *dbuf;
710	int c;
711	char *cp;
712
713	if (fsize(fp) == 0)
714		return;
715	cp = getdeadletter();
716	c = umask(077);
717	dbuf = Fopen(cp, "a");
718	(void)umask(c);
719	if (dbuf == NULL)
720		return;
721	while ((c = getc(fp)) != EOF)
722		(void)putc(c, dbuf);
723	(void)Fclose(dbuf);
724	rewind(fp);
725}
726