xref: /freebsd/usr.bin/mail/collect.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
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
36 static char sccsid[] = "@(#)collect.c	8.2 (Berkeley) 4/19/94";
37 #endif
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD$");
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 
64 static	sig_t	saveint;		/* Previous SIGINT value */
65 static	sig_t	savehup;		/* Previous SIGHUP value */
66 static	sig_t	savetstp;		/* Previous SIGTSTP value */
67 static	sig_t	savettou;		/* Previous SIGTTOU value */
68 static	sig_t	savettin;		/* Previous SIGTTIN value */
69 static	FILE	*collf;			/* File for saving away */
70 static	int	hadintr;		/* Have seen one SIGINT so far */
71 
72 static	jmp_buf	colljmp;		/* To get back to work */
73 static	int	colljmp_p;		/* whether to long jump */
74 static	jmp_buf	collabort;		/* To end collection with error */
75 
76 FILE *
77 collect(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 		 */
152 cont:
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;
479 err:
480 	if (collf != NULL) {
481 		(void)Fclose(collf);
482 		collf = NULL;
483 	}
484 out:
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  */
501 int
502 exwrite(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  */
549 void
550 mesedit(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  */
571 void
572 mespipe(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);
611 out:
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  */
623 int
624 forward(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*/
671 void
672 collstop(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*/
696 void
697 collint(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*/
720 void
721 collhup(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 
733 void
734 savedeadletter(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