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