xref: /original-bsd/usr.bin/mail/collect.c (revision 1f3a482a)
1 #
2 
3 /*
4  * Mail -- a mail program
5  *
6  * Collect input from standard input, handling
7  * ~ escapes.
8  */
9 
10 static char *SccsId = "@(#)collect.c	2.1 07/01/81";
11 
12 #include "rcv.h"
13 #include <sys/stat.h>
14 
15 /*
16  * Read a message from standard output and return a read file to it
17  * or NULL on error.
18  */
19 
20 /*
21  * The following hokiness with global variables is so that on
22  * receipt of an interrupt signal, the partial message can be salted
23  * away on dead.letter.  The output file must be available to flush,
24  * and the input to read.  Several open files could be saved all through
25  * Mail if stdio allowed simultaneous read/write access.
26  */
27 
28 static	int	(*savesig)();		/* Previous SIGINT value */
29 static	int	(*savehup)();		/* Previous SIGHUP value */
30 static	int	(*savecont)();		/* Previous SIGCONT value */
31 static	FILE	*newi;			/* File for saving away */
32 static	FILE	*newo;			/* Output side of same */
33 static	int	hf;			/* Ignore interrups */
34 static	int	hadintr;		/* Have seen one SIGINT so far */
35 
36 static	jmp_buf	coljmp;			/* To get back to work */
37 
38 FILE *
39 collect(hp)
40 	struct header *hp;
41 {
42 	FILE *ibuf, *fbuf, *obuf;
43 	int lc, cc, escape, collrub(), intack(), stopdot, collhup, collcont();
44 	register int c, t;
45 	char linebuf[LINESIZE], *cp;
46 	extern char tempMail[];
47 	int notify();
48 
49 	noreset++;
50 	stopdot = (value("dot") != NOSTR) && intty;
51 	ibuf = obuf = NULL;
52 	if (value("ignore") != NOSTR)
53 		hf = 1;
54 	else
55 		hf = 0;
56 	hadintr = 0;
57 	if ((savesig = sigset(SIGINT, SIG_IGN)) != SIG_IGN)
58 		sigset(SIGINT, hf ? intack : collrub), sighold(SIGINT);
59 	if ((savehup = sigset(SIGHUP, SIG_IGN)) != SIG_IGN)
60 		sigset(SIGHUP, collrub), sighold(SIGINT);
61 	savecont = sigset(SIGCONT, collcont);
62 	newi = NULL;
63 	newo = NULL;
64 	if ((obuf = fopen(tempMail, "w")) == NULL) {
65 		perror(tempMail);
66 		goto err;
67 	}
68 	newo = obuf;
69 	if ((ibuf = fopen(tempMail, "r")) == NULL) {
70 		perror(tempMail);
71 		newo = NULL;
72 		fclose(obuf);
73 		goto err;
74 	}
75 	newi = ibuf;
76 	remove(tempMail);
77 
78 	/*
79 	 * If we are going to prompt for a subject,
80 	 * refrain from printing a newline after
81 	 * the headers (since some people mind).
82 	 */
83 
84 	t = GTO|GSUBJECT|GCC|GNL;
85 	c = 0;
86 	if (intty && sflag == NOSTR && hp->h_subject == NOSTR && value("ask"))
87 		t &= ~GNL, c++;
88 	if (hp->h_seq != 0) {
89 		puthead(hp, stdout, t);
90 		fflush(stdout);
91 	}
92 	if (c)
93 		grabh(hp, GSUBJECT);
94 	escape = ESCAPE;
95 	if ((cp = value("escape")) != NOSTR)
96 		escape = *cp;
97 	for (;;) {
98 		setjmp(coljmp);
99 		sigrelse(SIGINT);
100 		sigrelse(SIGHUP);
101 		flush();
102 		if (readline(stdin, linebuf) <= 0)
103 			break;
104 		hadintr = 0;
105 		if (stopdot && equal(".", linebuf))
106 			break;
107 		if (linebuf[0] != escape || rflag != NOSTR) {
108 			if ((t = putline(obuf, linebuf)) < 0)
109 				goto err;
110 			continue;
111 		}
112 		c = linebuf[1];
113 		switch (c) {
114 		default:
115 			/*
116 			 * On double escape, just send the single one.
117 			 * Otherwise, it's an error.
118 			 */
119 
120 			if (c == escape) {
121 				if (putline(obuf, &linebuf[1]) < 0)
122 					goto err;
123 				else
124 					break;
125 			}
126 			printf("Unknown tilde escape.\n");
127 			break;
128 
129 		case 'C':
130 			/*
131 			 * Dump core.
132 			 */
133 
134 			core();
135 			break;
136 
137 		case '!':
138 			/*
139 			 * Shell escape, send the balance of the
140 			 * line to sh -c.
141 			 */
142 
143 			shell(&linebuf[2]);
144 			break;
145 
146 		case ':':
147 		case '_':
148 			/*
149 			 * Escape to command mode, but be nice!
150 			 */
151 
152 			execute(&linebuf[2], 1);
153 			break;
154 
155 		case '.':
156 			/*
157 			 * Simulate end of file on input.
158 			 */
159 			goto eof;
160 
161 		case 'q':
162 		case 'Q':
163 			/*
164 			 * Force a quit of sending mail.
165 			 * Act like an interrupt happened.
166 			 */
167 
168 			hadintr++;
169 			collrub(SIGINT);
170 			exit(1);
171 
172 		case 'h':
173 			/*
174 			 * Grab a bunch of headers.
175 			 */
176 			if (!intty || !outtty) {
177 				printf("~h: no can do!?\n");
178 				break;
179 			}
180 			grabh(hp, GTO|GSUBJECT|GCC|GBCC);
181 			printf("(continue)\n");
182 			break;
183 
184 		case 't':
185 			/*
186 			 * Add to the To list.
187 			 */
188 
189 			hp->h_to = addto(hp->h_to, &linebuf[2]);
190 			hp->h_seq++;
191 			break;
192 
193 		case 's':
194 			/*
195 			 * Set the Subject list.
196 			 */
197 
198 			cp = &linebuf[2];
199 			while (any(*cp, " \t"))
200 				cp++;
201 			hp->h_subject = savestr(cp);
202 			hp->h_seq++;
203 			break;
204 
205 		case 'c':
206 			/*
207 			 * Add to the CC list.
208 			 */
209 
210 			hp->h_cc = addto(hp->h_cc, &linebuf[2]);
211 			hp->h_seq++;
212 			break;
213 
214 		case 'b':
215 			/*
216 			 * Add stuff to blind carbon copies list.
217 			 */
218 			hp->h_bcc = addto(hp->h_bcc, &linebuf[2]);
219 			hp->h_seq++;
220 			break;
221 
222 		case 'd':
223 			copy(deadletter, &linebuf[2]);
224 			/* fall into . . . */
225 
226 		case 'r':
227 			/*
228 			 * Invoke a file:
229 			 * Search for the file name,
230 			 * then open it and copy the contents to obuf.
231 			 */
232 
233 			cp = &linebuf[2];
234 			while (any(*cp, " \t"))
235 				cp++;
236 			if (*cp == '\0') {
237 				printf("Interpolate what file?\n");
238 				break;
239 			}
240 			cp = expand(cp);
241 			if (cp == NOSTR)
242 				break;
243 			if (isdir(cp)) {
244 				printf("%s: directory\n");
245 				break;
246 			}
247 			if ((fbuf = fopen(cp, "r")) == NULL) {
248 				perror(cp);
249 				break;
250 			}
251 			printf("\"%s\" ", cp);
252 			flush();
253 			lc = 0;
254 			cc = 0;
255 			while (readline(fbuf, linebuf) > 0) {
256 				lc++;
257 				if ((t = putline(obuf, linebuf)) < 0) {
258 					fclose(fbuf);
259 					goto err;
260 				}
261 				cc += t;
262 			}
263 			fclose(fbuf);
264 			printf("%d/%d\n", lc, cc);
265 			break;
266 
267 		case 'w':
268 			/*
269 			 * Write the message on a file.
270 			 */
271 
272 			cp = &linebuf[2];
273 			while (any(*cp, " \t"))
274 				cp++;
275 			if (*cp == '\0') {
276 				fprintf(stderr, "Write what file!?\n");
277 				break;
278 			}
279 			if ((cp = expand(cp)) == NOSTR)
280 				break;
281 			fflush(obuf);
282 			rewind(ibuf);
283 			exwrite(cp, ibuf, 1);
284 			break;
285 
286 		case 'm':
287 		case 'f':
288 			/*
289 			 * Interpolate the named messages, if we
290 			 * are in receiving mail mode.  Does the
291 			 * standard list processing garbage.
292 			 * If ~f is given, we don't shift over.
293 			 */
294 
295 			if (!rcvmode) {
296 				printf("No messages to send from!?!\n");
297 				break;
298 			}
299 			cp = &linebuf[2];
300 			while (any(*cp, " \t"))
301 				cp++;
302 			if (forward(cp, obuf, c) < 0)
303 				goto err;
304 			printf("(continue)\n");
305 			break;
306 
307 		case '?':
308 			if ((fbuf = fopen(THELPFILE, "r")) == NULL) {
309 				printf("No help just now.\n");
310 				break;
311 			}
312 			t = getc(fbuf);
313 			while (t != -1) {
314 				putchar(t);
315 				t = getc(fbuf);
316 			}
317 			fclose(fbuf);
318 			break;
319 
320 		case 'p':
321 			/*
322 			 * Print out the current state of the
323 			 * message without altering anything.
324 			 */
325 
326 			fflush(obuf);
327 			rewind(ibuf);
328 			printf("-------\nMessage contains:\n");
329 			puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
330 			t = getc(ibuf);
331 			while (t != EOF) {
332 				putchar(t);
333 				t = getc(ibuf);
334 			}
335 			printf("(continue)\n");
336 			break;
337 
338 		case '^':
339 		case '|':
340 			/*
341 			 * Pipe message through command.
342 			 * Collect output as new message.
343 			 */
344 
345 			obuf = mespipe(ibuf, obuf, &linebuf[2]);
346 			newo = obuf;
347 			ibuf = newi;
348 			newi = ibuf;
349 			printf("(continue)\n");
350 			break;
351 
352 		case 'v':
353 		case 'e':
354 			/*
355 			 * Edit the current message.
356 			 * 'e' means to use EDITOR
357 			 * 'v' means to use VISUAL
358 			 */
359 
360 			if ((obuf = mesedit(ibuf, obuf, c)) == NULL)
361 				goto err;
362 			newo = obuf;
363 			ibuf = newi;
364 			printf("(continue)\n");
365 			break;
366 			break;
367 		}
368 	}
369 eof:
370 	fclose(obuf);
371 	rewind(ibuf);
372 	sigset(SIGINT, savesig);
373 	sigset(SIGHUP, savehup);
374 	sigset(SIGCONT, savecont);
375 	noreset = 0;
376 	return(ibuf);
377 
378 err:
379 	if (ibuf != NULL)
380 		fclose(ibuf);
381 	if (obuf != NULL)
382 		fclose(obuf);
383 	sigset(SIGINT, savesig);
384 	sigset(SIGHUP, savehup);
385 	sigset(SIGCONT, savecont);
386 	noreset = 0;
387 	return(NULL);
388 }
389 
390 /*
391  * Non destructively interrogate the value of the given signal.
392  */
393 
394 psig(n)
395 {
396 	register (*wassig)();
397 
398 	wassig = sigset(n, SIG_IGN);
399 	sigset(n, wassig);
400 	return((int) wassig);
401 }
402 
403 /*
404  * Write a file, ex-like if f set.
405  */
406 
407 exwrite(name, ibuf, f)
408 	char name[];
409 	FILE *ibuf;
410 {
411 	register FILE *of;
412 	register int c;
413 	long cc;
414 	int lc;
415 	struct stat junk;
416 
417 	if (f) {
418 		printf("\"%s\" ", name);
419 		fflush(stdout);
420 	}
421 	if (stat(name, &junk) >= 0) {
422 		if (!f)
423 			fprintf(stderr, "%s: ", name);
424 		fprintf(stderr, "File exists\n", name);
425 		return(-1);
426 	}
427 	if ((of = fopen(name, "w")) == NULL) {
428 		perror(NOSTR);
429 		return(-1);
430 	}
431 	lc = 0;
432 	cc = 0;
433 	while ((c = getc(ibuf)) != EOF) {
434 		cc++;
435 		if (c == '\n')
436 			lc++;
437 		putc(c, of);
438 		if (ferror(of)) {
439 			perror(name);
440 			fclose(of);
441 			return(-1);
442 		}
443 	}
444 	fclose(of);
445 	printf("%d/%ld\n", lc, cc);
446 	fflush(stdout);
447 	return(0);
448 }
449 
450 /*
451  * Edit the message being collected on ibuf and obuf.
452  * Write the message out onto some poorly-named temp file
453  * and point an editor at it.
454  *
455  * On return, make the edit file the new temp file.
456  */
457 
458 FILE *
459 mesedit(ibuf, obuf, c)
460 	FILE *ibuf, *obuf;
461 {
462 	int pid, s;
463 	FILE *fbuf;
464 	register int t;
465 	int (*sig)(), (*scont)(), foonly();
466 	struct stat sbuf;
467 	extern char tempMail[], tempEdit[];
468 	register char *edit;
469 
470 	sig = sigset(SIGINT, SIG_IGN);
471 	scont = sigset(SIGCONT, foonly);
472 	if (stat(tempEdit, &sbuf) >= 0) {
473 		printf("%s: file exists\n", tempEdit);
474 		goto out;
475 	}
476 	close(creat(tempEdit, 0600));
477 	if ((fbuf = fopen(tempEdit, "w")) == NULL) {
478 		perror(tempEdit);
479 		goto out;
480 	}
481 	fflush(obuf);
482 	rewind(ibuf);
483 	t = getc(ibuf);
484 	while (t != EOF) {
485 		putc(t, fbuf);
486 		t = getc(ibuf);
487 	}
488 	fflush(fbuf);
489 	if (ferror(fbuf)) {
490 		perror(tempEdit);
491 		remove(tempEdit);
492 		goto fix;
493 	}
494 	fclose(fbuf);
495 	if ((edit = value(c == 'e' ? "EDITOR" : "VISUAL")) == NOSTR)
496 		edit = c == 'e' ? EDITOR : VISUAL;
497 	pid = vfork();
498 	if (pid == 0) {
499 		if (sig != SIG_IGN)
500 			sigsys(SIGINT, SIG_DFL);
501 		execl(edit, edit, tempEdit, 0);
502 		perror(edit);
503 		_exit(1);
504 	}
505 	if (pid == -1) {
506 		perror("fork");
507 		remove(tempEdit);
508 		goto out;
509 	}
510 	while (wait(&s) != pid)
511 		;
512 	if (s != 0) {
513 		printf("Fatal error in \"%s\"\n", edit);
514 		remove(tempEdit);
515 		goto out;
516 	}
517 
518 	/*
519 	 * Now switch to new file.
520 	 */
521 
522 	if ((fbuf = fopen(tempEdit, "a")) == NULL) {
523 		perror(tempEdit);
524 		remove(tempEdit);
525 		goto out;
526 	}
527 	if ((ibuf = fopen(tempEdit, "r")) == NULL) {
528 		perror(tempEdit);
529 		fclose(fbuf);
530 		remove(tempEdit);
531 		goto out;
532 	}
533 	remove(tempEdit);
534 	fclose(obuf);
535 	fclose(newi);
536 	obuf = fbuf;
537 	goto out;
538 fix:
539 	perror(tempEdit);
540 out:
541 	sigset(SIGCONT, scont);
542 	sigset(SIGINT, sig);
543 	newi = ibuf;
544 	return(obuf);
545 }
546 
547 /*
548  * Currently, Berkeley virtual VAX/UNIX will not let you change the
549  * disposition of SIGCONT, except to trap it somewhere new.
550  * Hence, sigset(SIGCONT, foonly) is used to ignore continue signals.
551  */
552 foonly() {}
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  */
560 
561 FILE *
562 mespipe(ibuf, obuf, cmd)
563 	FILE *ibuf, *obuf;
564 	char cmd[];
565 {
566 	register FILE *ni, *no;
567 	int pid, s;
568 	int (*savesig)();
569 	char *Shell;
570 
571 	newi = ibuf;
572 	if ((no = fopen(tempEdit, "w")) == NULL) {
573 		perror(tempEdit);
574 		return(obuf);
575 	}
576 	if ((ni = fopen(tempEdit, "r")) == NULL) {
577 		perror(tempEdit);
578 		fclose(no);
579 		remove(tempEdit);
580 		return(obuf);
581 	}
582 	remove(tempEdit);
583 	savesig = sigset(SIGINT, SIG_IGN);
584 	fflush(obuf);
585 	rewind(ibuf);
586 	if ((Shell = value("SHELL")) == NULL)
587 		Shell = "/bin/sh";
588 	if ((pid = vfork()) == -1) {
589 		perror("fork");
590 		goto err;
591 	}
592 	if (pid == 0) {
593 		/*
594 		 * stdin = current message.
595 		 * stdout = new message.
596 		 */
597 
598 		close(0);
599 		dup(fileno(ibuf));
600 		close(1);
601 		dup(fileno(no));
602 		for (s = 4; s < 15; s++)
603 			close(s);
604 		execl(Shell, Shell, "-c", cmd, 0);
605 		perror(Shell);
606 		_exit(1);
607 	}
608 	while (wait(&s) != pid)
609 		;
610 	if (s != 0 || pid == -1) {
611 		fprintf(stderr, "\"%s\" failed!?\n", cmd);
612 		goto err;
613 	}
614 	if (fsize(ni) == 0) {
615 		fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
616 		goto err;
617 	}
618 
619 	/*
620 	 * Take new files.
621 	 */
622 
623 	newi = ni;
624 	fclose(ibuf);
625 	fclose(obuf);
626 	sigset(SIGINT, savesig);
627 	return(no);
628 
629 err:
630 	fclose(no);
631 	fclose(ni);
632 	sigset(SIGINT, savesig);
633 	return(obuf);
634 }
635 
636 /*
637  * Interpolate the named messages into the current
638  * message, preceding each line with a tab.
639  * Return a count of the number of characters now in
640  * the message, or -1 if an error is encountered writing
641  * the message temporary.  The flag argument is 'm' if we
642  * should shift over and 'f' if not.
643  */
644 
645 forward(ms, obuf, f)
646 	char ms[];
647 	FILE *obuf;
648 {
649 	register int *msgvec, *ip;
650 	extern char tempMail[];
651 
652 	msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
653 	if (msgvec == (int *) NOSTR)
654 		return(0);
655 	if (getmsglist(ms, msgvec, 0) < 0)
656 		return(0);
657 	if (*msgvec == NULL) {
658 		*msgvec = first(0, MMNORM);
659 		if (*msgvec == NULL) {
660 			printf("No appropriate messages\n");
661 			return(0);
662 		}
663 		msgvec[1] = NULL;
664 	}
665 	printf("Interpolating:");
666 	for (ip = msgvec; *ip != NULL; ip++) {
667 		touch(*ip);
668 		printf(" %d", *ip);
669 		if (f == 'm') {
670 			if (transmit(&message[*ip-1], obuf) < 0) {
671 				perror(tempMail);
672 				return(-1);
673 			}
674 		} else
675 			if (send(&message[*ip-1], obuf) < 0) {
676 				perror(tempMail);
677 				return(-1);
678 			}
679 	}
680 	printf("\n");
681 	return(0);
682 }
683 
684 /*
685  * Send message described by the passed pointer to the
686  * passed output buffer.  Insert a tab in front of each
687  * line.  Return a count of the characters sent, or -1
688  * on error.
689  */
690 
691 transmit(mailp, obuf)
692 	struct message *mailp;
693 	FILE *obuf;
694 {
695 	register struct message *mp;
696 	register int c, ch;
697 	int n, bol;
698 	FILE *ibuf;
699 
700 	mp = mailp;
701 	ibuf = setinput(mp);
702 	c = msize(mp);
703 	n = c;
704 	bol = 1;
705 	while (c-- > 0) {
706 		if (bol) {
707 			bol = 0;
708 			putc('\t', obuf);
709 			n++;
710 			if (ferror(obuf)) {
711 				perror("/tmp");
712 				return(-1);
713 			}
714 		}
715 		ch = getc(ibuf);
716 		if (ch == '\n')
717 			bol++;
718 		putc(ch, obuf);
719 		if (ferror(obuf)) {
720 			perror("/tmp");
721 			return(-1);
722 		}
723 	}
724 	return(n);
725 }
726 
727 /*
728  * Print (continue) when continued after ^Z.
729  */
730 collcont(s)
731 {
732 
733 	printf("(continue)\n");
734 	fflush(stdout);
735 }
736 
737 /*
738  * On interrupt, go here to save the partial
739  * message on ~/dead.letter.
740  * Then restore signals and execute the normal
741  * signal routine.  We only come here if signals
742  * were previously set anyway.
743  */
744 
745 collrub(s)
746 {
747 	register FILE *dbuf;
748 	register int c;
749 
750 	if (s == SIGINT && hadintr == 0) {
751 		hadintr++;
752 		clrbuf(stdout);
753 		printf("\n(Interrupt -- one more to kill letter)\n");
754 		sigrelse(s);
755 		longjmp(coljmp, 1);
756 	}
757 	fclose(newo);
758 	rewind(newi);
759 	if (s == SIGINT && value("nosave") != NOSTR || fsize(newi) == 0)
760 		goto done;
761 	if ((dbuf = fopen(deadletter, "w")) == NULL)
762 		goto done;
763 	chmod(deadletter, 0600);
764 	while ((c = getc(newi)) != EOF)
765 		putc(c, dbuf);
766 	fclose(dbuf);
767 
768 done:
769 	fclose(newi);
770 	sigset(SIGINT, savesig);
771 	sigset(SIGHUP, savehup);
772 	sigset(SIGCONT, savecont);
773 	if (rcvmode) {
774 		if (s == SIGHUP)
775 			hangup(SIGHUP);
776 		else
777 			stop(s);
778 	}
779 	else
780 		exit(1);
781 }
782 
783 /*
784  * Acknowledge an interrupt signal from the tty by typing an @
785  */
786 
787 intack(s)
788 {
789 
790 	sigrelse(SIGCONT);
791 	puts("@");
792 	fflush(stdout);
793 	clearerr(stdin);
794 }
795 
796 /*
797  * Add a string to the end of a header entry field.
798  */
799 
800 char *
801 addto(hf, news)
802 	char hf[], news[];
803 {
804 	register char *cp, *cp2, *linebuf;
805 
806 	if (hf == NOSTR)
807 		hf = "";
808 	if (*news == '\0')
809 		return(hf);
810 	linebuf = salloc(strlen(hf) + strlen(news) + 2);
811 	for (cp = hf; any(*cp, " \t"); cp++)
812 		;
813 	for (cp2 = linebuf; *cp;)
814 		*cp2++ = *cp++;
815 	*cp2++ = ' ';
816 	for (cp = news; any(*cp, " \t"); cp++)
817 		;
818 	while (*cp != '\0')
819 		*cp2++ = *cp++;
820 	*cp2 = '\0';
821 	return(linebuf);
822 }
823