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