xref: /original-bsd/usr.bin/mail/collect.c (revision ba72ef4c)
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.1 10/08/80";
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	FILE	*newi;			/* File for saving away */
30 static	FILE	*newo;			/* Output side of same */
31 static	int	hf;			/* Ignore interrups */
32 static	int	nofault;		/* Soft signal if set */
33 static	int	hadintr;		/* Have seen one SIGINT so far */
34 
35 static	jmp_buf	coljmp;			/* To get back to work */
36 
37 FILE *
38 collect(hp)
39 	struct header *hp;
40 {
41 	FILE *ibuf, *fbuf, *obuf;
42 	int lc, cc, escape, collrub(), intack(), stopdot;
43 	register int c, t;
44 	char linebuf[LINESIZE], *cp;
45 	extern char tempMail[];
46 
47 	noreset++;
48 	stopdot = (value("dot") != NOSTR) && intty;
49 	ibuf = obuf = NULL;
50 	if (value("ignore") != NOSTR)
51 		hf = 1;
52 	else
53 		hf = 0;
54 	nofault = 1;
55 	hadintr = 0;
56 	if ((savesig = signal(SIGINT, SIG_IGN)) != SIG_IGN)
57 		signal(SIGINT, hf ? intack : collrub);
58 	newi = NULL;
59 	newo = NULL;
60 	if ((obuf = fopen(tempMail, "w")) == NULL) {
61 		perror(tempMail);
62 		goto err;
63 	}
64 	newo = obuf;
65 	if ((ibuf = fopen(tempMail, "r")) == NULL) {
66 		perror(tempMail);
67 		newo = NULL;
68 		fclose(obuf);
69 		goto err;
70 	}
71 	newi = ibuf;
72 	remove(tempMail);
73 
74 	/*
75 	 * If we are going to prompt for a subject,
76 	 * refrain from printing a newline after
77 	 * the headers (since some people mind).
78 	 */
79 
80 	t = GTO|GSUBJECT|GCC|GNL;
81 	c = 0;
82 	if (intty && sflag == NOSTR && hp->h_subject == NOSTR && value("ask"))
83 		t &= ~GNL, c++;
84 	if (hp->h_seq != 0) {
85 		puthead(hp, stdout, t);
86 		fflush(stdout);
87 	}
88 	if (c)
89 		grabh(hp, GSUBJECT);
90 	escape = ESCAPE;
91 	if ((cp = value("escape")) != NOSTR)
92 		escape = *cp;
93 	for (;;) {
94 		setjmp(coljmp);
95 		nofault = 0;
96 		flush();
97 		if (readline(stdin, linebuf) <= 0)
98 			break;
99 		hadintr = 0;
100 		if (stopdot && equal(".", linebuf))
101 			break;
102 		if (linebuf[0] != escape ||
103 		    (!intty && value("henry") == NOSTR)) {
104 			if ((t = putline(obuf, linebuf)) < 0)
105 				goto err;
106 			continue;
107 		}
108 		c = linebuf[1];
109 		nofault= 0;
110 		switch (c) {
111 		default:
112 			/*
113 			 * On double escape, just send the single one.
114 			 * Otherwise, it's an error.
115 			 */
116 
117 			if (c == escape) {
118 				if (putline(obuf, &linebuf[1]) < 0)
119 					goto err;
120 				else
121 					break;
122 			}
123 			printf("Unknown tilde escape.\n");
124 			break;
125 
126 		case 'C':
127 			/*
128 			 * Dump core.
129 			 */
130 
131 			core();
132 			break;
133 
134 		case '!':
135 			/*
136 			 * Shell escape, send the balance of the
137 			 * line to sh -c.
138 			 */
139 
140 			shell(&linebuf[2]);
141 			break;
142 
143 		case ':':
144 		case '_':
145 			/*
146 			 * Escape to command mode, but be nice!
147 			 */
148 
149 			nofault = 0;
150 			execute(&linebuf[2]);
151 			break;
152 
153 		case '.':
154 			/*
155 			 * Simulate end of file on input.
156 			 */
157 			goto eof;
158 
159 		case 'q':
160 		case 'Q':
161 			/*
162 			 * Force a quit of sending mail.
163 			 * Act like an interrupt happened.
164 			 */
165 
166 			nofault = 0;
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 			nofault = 0;
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 			nofault = 0;
329 			printf("-------\nMessage contains:\n");
330 			puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
331 			t = getc(ibuf);
332 			while (t != EOF) {
333 				putchar(t);
334 				t = getc(ibuf);
335 			}
336 			printf("(continue)\n");
337 			break;
338 
339 		case '^':
340 		case '|':
341 			/*
342 			 * Pipe message through command.
343 			 * Collect output as new message.
344 			 */
345 
346 			obuf = mespipe(ibuf, obuf, &linebuf[2]);
347 			newo = obuf;
348 			ibuf = newi;
349 			newi = ibuf;
350 			printf("(continue)\n");
351 			break;
352 
353 		case 'v':
354 		case 'e':
355 			/*
356 			 * Edit the current message.
357 			 * 'e' means to use EDITOR
358 			 * 'v' means to use VISUAL
359 			 */
360 
361 			if ((obuf = mesedit(ibuf, obuf, c)) == NULL)
362 				goto err;
363 			newo = obuf;
364 			ibuf = newi;
365 			printf("(continue)\n");
366 			break;
367 			break;
368 		}
369 	}
370 eof:
371 	fclose(obuf);
372 	rewind(ibuf);
373 	signal(SIGINT, savesig);
374 	noreset = 0;
375 	return(ibuf);
376 
377 err:
378 	if (ibuf != NULL)
379 		fclose(ibuf);
380 	if (obuf != NULL)
381 		fclose(obuf);
382 	signal(SIGINT, savesig);
383 	noreset = 0;
384 	return(NULL);
385 }
386 
387 /*
388  * Non destructively interrogate the value of the given signal.
389  */
390 
391 psig(n)
392 {
393 	register (*wassig)();
394 
395 	wassig = signal(n, SIG_IGN);
396 	signal(n, wassig);
397 	return((int) wassig);
398 }
399 
400 /*
401  * Write a file, ex-like if f set.
402  */
403 
404 exwrite(name, ibuf, f)
405 	char name[];
406 	FILE *ibuf;
407 {
408 	register FILE *of;
409 	register int c;
410 	long cc;
411 	int lc;
412 	struct stat junk;
413 
414 	if (f) {
415 		printf("\"%s\" ", name);
416 		fflush(stdout);
417 	}
418 	if (stat(name, &junk) >= 0) {
419 		if (!f)
420 			fprintf(stderr, "%s: ", name);
421 		fprintf(stderr, "File exists\n", name);
422 		return(-1);
423 	}
424 	if ((of = fopen(name, "w")) == NULL) {
425 		perror(NOSTR);
426 		return(-1);
427 	}
428 	lc = 0;
429 	cc = 0;
430 	while ((c = getc(ibuf)) != EOF) {
431 		cc++;
432 		if (c == '\n')
433 			lc++;
434 		putc(c, of);
435 		if (ferror(of)) {
436 			perror(name);
437 			fclose(of);
438 			return(-1);
439 		}
440 	}
441 	fclose(of);
442 	printf("%d/%ld\n", lc, cc);
443 	fflush(stdout);
444 	return(0);
445 }
446 
447 /*
448  * Edit the message being collected on ibuf and obuf.
449  * Write the message out onto some poorly-named temp file
450  * and point an editor at it.
451  *
452  * On return, make the edit file the new temp file.
453  */
454 
455 FILE *
456 mesedit(ibuf, obuf, c)
457 	FILE *ibuf, *obuf;
458 {
459 	int pid, s;
460 	FILE *fbuf;
461 	register int t;
462 	int (*sig)();
463 	struct stat sbuf;
464 	extern char tempMail[], tempEdit[];
465 	register char *edit;
466 
467 	sig = signal(SIGINT, SIG_IGN);
468 	if (stat(tempEdit, &sbuf) >= 0) {
469 		printf("%s: file exists\n", tempEdit);
470 		goto out;
471 	}
472 	close(creat(tempEdit, 0600));
473 	if ((fbuf = fopen(tempEdit, "w")) == NULL) {
474 		perror(tempEdit);
475 		goto out;
476 	}
477 	fflush(obuf);
478 	rewind(ibuf);
479 	t = getc(ibuf);
480 	while (t != EOF) {
481 		putc(t, fbuf);
482 		t = getc(ibuf);
483 	}
484 	fflush(fbuf);
485 	if (ferror(fbuf)) {
486 		perror(tempEdit);
487 		remove(tempEdit);
488 		goto fix;
489 	}
490 	fclose(fbuf);
491 	if ((edit = value(c == 'e' ? "EDITOR" : "VISUAL")) == NOSTR)
492 		edit = c == 'e' ? EDITOR : VISUAL;
493 	pid = vfork();
494 	if (pid == 0) {
495 		if (sig != SIG_IGN)
496 			signal(SIGINT, SIG_DFL);
497 		execl(edit, edit, tempEdit, 0);
498 		perror(edit);
499 		_exit(1);
500 	}
501 	if (pid == -1) {
502 		perror("fork");
503 		remove(tempEdit);
504 		goto out;
505 	}
506 	while (wait(&s) != pid)
507 		;
508 	if (s != 0) {
509 		printf("Fatal error in \"%s\"\n", edit);
510 		remove(tempEdit);
511 		goto out;
512 	}
513 
514 	/*
515 	 * Now switch to new file.
516 	 */
517 
518 	if ((fbuf = fopen(tempEdit, "a")) == NULL) {
519 		perror(tempEdit);
520 		remove(tempEdit);
521 		goto out;
522 	}
523 	if ((ibuf = fopen(tempEdit, "r")) == NULL) {
524 		perror(tempEdit);
525 		fclose(fbuf);
526 		remove(tempEdit);
527 		goto out;
528 	}
529 	remove(tempEdit);
530 	fclose(obuf);
531 	fclose(newi);
532 	obuf = fbuf;
533 	goto out;
534 fix:
535 	perror(tempEdit);
536 out:
537 	signal(SIGINT, sig);
538 	newi = ibuf;
539 	return(obuf);
540 }
541 
542 /*
543  * Pipe the message through the command.
544  * Old message is on stdin of command;
545  * New message collected from stdout.
546  * Sh -c must return 0 to accept the new message.
547  */
548 
549 FILE *
550 mespipe(ibuf, obuf, cmd)
551 	FILE *ibuf, *obuf;
552 	char cmd[];
553 {
554 	register FILE *ni, *no;
555 	int pid, s;
556 	int (*savesig)();
557 	char *Shell;
558 
559 	newi = ibuf;
560 	if ((no = fopen(tempEdit, "w")) == NULL) {
561 		perror(tempEdit);
562 		return(obuf);
563 	}
564 	if ((ni = fopen(tempEdit, "r")) == NULL) {
565 		perror(tempEdit);
566 		fclose(no);
567 		remove(tempEdit);
568 		return(obuf);
569 	}
570 	remove(tempEdit);
571 	savesig = signal(SIGINT, SIG_IGN);
572 	fflush(obuf);
573 	rewind(ibuf);
574 	if ((Shell = value("SHELL")) == NULL)
575 		Shell = "/bin/sh";
576 	if ((pid = vfork()) == -1) {
577 		perror("fork");
578 		goto err;
579 	}
580 	if (pid == 0) {
581 		/*
582 		 * stdin = current message.
583 		 * stdout = new message.
584 		 */
585 
586 		close(0);
587 		dup(fileno(ibuf));
588 		close(1);
589 		dup(fileno(no));
590 		for (s = 4; s < 15; s++)
591 			close(s);
592 		execl(Shell, Shell, "-c", cmd, 0);
593 		perror(Shell);
594 		_exit(1);
595 	}
596 	while (wait(&s) != pid)
597 		;
598 	if (s != 0 || pid == -1) {
599 		fprintf(stderr, "\"%s\" failed!?\n", cmd);
600 		goto err;
601 	}
602 	if (fsize(ni) == 0) {
603 		fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
604 		goto err;
605 	}
606 
607 	/*
608 	 * Take new files.
609 	 */
610 
611 	newi = ni;
612 	fclose(ibuf);
613 	fclose(obuf);
614 	signal(SIGINT, savesig);
615 	return(no);
616 
617 err:
618 	fclose(no);
619 	fclose(ni);
620 	signal(SIGINT, savesig);
621 	return(obuf);
622 }
623 
624 /*
625  * Interpolate the named messages into the current
626  * message, preceding each line with a tab.
627  * Return a count of the number of characters now in
628  * the message, or -1 if an error is encountered writing
629  * the message temporary.  The flag argument is 'm' if we
630  * should shift over and 'f' if not.
631  */
632 
633 forward(ms, obuf, f)
634 	char ms[];
635 	FILE *obuf;
636 {
637 	register int *msgvec, *ip;
638 	extern char tempMail[];
639 
640 	msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
641 	if (msgvec == (int *) NOSTR)
642 		return(0);
643 	if (getmsglist(ms, msgvec, 0) < 0)
644 		return(0);
645 	if (*msgvec == NULL) {
646 		*msgvec = first(0, MMNORM);
647 		if (*msgvec == NULL) {
648 			printf("No appropriate messages\n");
649 			return(0);
650 		}
651 		msgvec[1] = NULL;
652 	}
653 	printf("Interpolating:");
654 	for (ip = msgvec; *ip != NULL; ip++) {
655 		touch(*ip);
656 		printf(" %d", *ip);
657 		if (f == 'm') {
658 			if (transmit(&message[*ip-1], obuf) < 0) {
659 				perror(tempMail);
660 				return(-1);
661 			}
662 		} else
663 			if (send(&message[*ip-1], obuf) < 0) {
664 				perror(tempMail);
665 				return(-1);
666 			}
667 	}
668 	printf("\n");
669 	return(0);
670 }
671 
672 /*
673  * Send message described by the passed pointer to the
674  * passed output buffer.  Insert a tab in front of each
675  * line.  Return a count of the characters sent, or -1
676  * on error.
677  */
678 
679 transmit(mailp, obuf)
680 	struct message *mailp;
681 	FILE *obuf;
682 {
683 	register struct message *mp;
684 	register int c, ch;
685 	int n, bol;
686 	FILE *ibuf;
687 
688 	mp = mailp;
689 	ibuf = setinput(mp);
690 	c = msize(mp);
691 	n = c;
692 	bol = 1;
693 	while (c-- > 0) {
694 		if (bol) {
695 			bol = 0;
696 			putc('\t', obuf);
697 			n++;
698 			if (ferror(obuf)) {
699 				perror("/tmp");
700 				return(-1);
701 			}
702 		}
703 		ch = getc(ibuf);
704 		if (ch == '\n')
705 			bol++;
706 		putc(ch, obuf);
707 		if (ferror(obuf)) {
708 			perror("/tmp");
709 			return(-1);
710 		}
711 	}
712 	return(n);
713 }
714 
715 /*
716  * On interrupt, go here to save the partial
717  * message on #/dead.letter.
718  * Then restore signals and execute the normal
719  * signal routine.  We only come here if signals
720  * were previously set anyway.
721  */
722 
723 collrub(s)
724 {
725 	register FILE *dbuf;
726 	register int c;
727 
728 #ifdef V7
729 	signal(s, SIG_IGN);
730 #else
731 	signal(SIGINT, SIG_IGN);
732 #endif
733 	if (nofault) {
734 #ifdef V7
735 		signal(s, collrub);
736 #else
737 		signal(SIGINT, collrub);
738 #endif
739 		return;
740 	}
741 	if (hadintr == 0) {
742 		hadintr++;
743 		clrbuf(stdout);
744 		printf("\n(Interrupt -- one more to kill letter)\n");
745 #ifdef V7
746 		signal(s, collrub);
747 #else
748 		signal(SIGINT, collrub);
749 #endif
750 		longjmp(coljmp, 1);
751 	}
752 	fclose(newo);
753 	rewind(newi);
754 	if (value("nosave") != NOSTR || fsize(newi) == 0)
755 		goto done;
756 	if ((dbuf = fopen(deadletter, "w")) == NULL)
757 		goto done;
758 	chmod(deadletter, 0600);
759 	while ((c = getc(newi)) != EOF)
760 		putc(c, dbuf);
761 	fclose(dbuf);
762 
763 done:
764 	fclose(newi);
765 	signal(SIGINT, savesig);
766 	if (rcvmode)
767 		stop();
768 	else
769 		exit(1);
770 }
771 
772 /*
773  * Acknowledge an interrupt signal from the tty by typing an @
774  */
775 
776 intack(s)
777 {
778 
779 	signal(SIGINT, SIG_IGN);
780 	puts("@");
781 	fflush(stdout);
782 	clearerr(stdin);
783 	signal(SIGINT, intack);
784 }
785 
786 /*
787  * Add a string to the end of a header entry field.
788  */
789 
790 char *
791 addto(hf, news)
792 	char hf[], news[];
793 {
794 	register char *cp, *cp2, *linebuf;
795 
796 	if (hf == NOSTR)
797 		hf = "";
798 	if (*news == '\0')
799 		return(hf);
800 	linebuf = salloc(strlen(hf) + strlen(news) + 2);
801 	for (cp = hf; any(*cp, " \t"); cp++)
802 		;
803 	for (cp2 = linebuf; *cp;)
804 		*cp2++ = *cp++;
805 	*cp2++ = ' ';
806 	for (cp = news; any(*cp, " \t"); cp++)
807 		;
808 	while (*cp != '\0')
809 		*cp2++ = *cp++;
810 	*cp2 = '\0';
811 	return(linebuf);
812 }
813