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