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