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