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