xref: /freebsd/usr.bin/mail/collect.c (revision 4b9d6057)
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 
54 static	sig_t	saveint;		/* Previous SIGINT value */
55 static	sig_t	savehup;		/* Previous SIGHUP value */
56 static	sig_t	savetstp;		/* Previous SIGTSTP value */
57 static	sig_t	savettou;		/* Previous SIGTTOU value */
58 static	sig_t	savettin;		/* Previous SIGTTIN value */
59 static	FILE	*collf;			/* File for saving away */
60 static	int	hadintr;		/* Have seen one SIGINT so far */
61 
62 static	jmp_buf	colljmp;		/* To get back to work */
63 static	int	colljmp_p;		/* whether to long jump */
64 static	jmp_buf	collabort;		/* To end collection with error */
65 
66 FILE *
67 collect(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 		 */
139 cont:
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;
466 err:
467 	if (collf != NULL) {
468 		(void)Fclose(collf);
469 		collf = NULL;
470 	}
471 out:
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  */
488 int
489 exwrite(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  */
533 void
534 mesedit(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  */
553 void
554 mespipe(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);
591 out:
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  */
603 int
604 forward(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*/
647 void
648 collstop(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*/
671 void
672 collint(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*/
694 void
695 collhup(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 
706 void
707 savedeadletter(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