xref: /netbsd-src/usr.bin/mail/collect.c (revision 76dfffe33547c37f8bdd446e3e4ab0f3c16cea4b)
1 /*	$NetBSD: collect.c,v 1.6 1996/06/08 19:48:16 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)collect.c	8.2 (Berkeley) 4/19/94";
39 #else
40 static char rcsid[] = "$NetBSD: collect.c,v 1.6 1996/06/08 19:48:16 christos Exp $";
41 #endif
42 #endif /* not lint */
43 
44 /*
45  * Mail -- a mail program
46  *
47  * Collect input from standard input, handling
48  * ~ escapes.
49  */
50 
51 #include "rcv.h"
52 #include "extern.h"
53 
54 /*
55  * Read a message from standard output and return a read file to it
56  * or NULL on error.
57  */
58 
59 /*
60  * The following hokiness with global variables is so that on
61  * receipt of an interrupt signal, the partial message can be salted
62  * away on dead.letter.
63  */
64 
65 static	sig_t	saveint;		/* Previous SIGINT value */
66 static	sig_t	savehup;		/* Previous SIGHUP value */
67 static	sig_t	savetstp;		/* Previous SIGTSTP value */
68 static	sig_t	savettou;		/* Previous SIGTTOU value */
69 static	sig_t	savettin;		/* Previous SIGTTIN value */
70 static	FILE	*collf;			/* File for saving away */
71 static	int	hadintr;		/* Have seen one SIGINT so far */
72 
73 static	jmp_buf	colljmp;		/* To get back to work */
74 static	int	colljmp_p;		/* whether to long jump */
75 static	jmp_buf	collabort;		/* To end collection with error */
76 
77 FILE *
78 collect(hp, printheaders)
79 	struct header *hp;
80 	int printheaders;
81 {
82 	FILE *fbuf;
83 	int lc, cc, escape, eofcount;
84 	register int c, t;
85 	char linebuf[LINESIZE], *cp;
86 	extern char *tempMail;
87 	char getsub;
88 	sigset_t oset, nset;
89 #if __GNUC__
90 	/* Avoid longjmp clobbering */
91 	(void) &escape;
92 	(void) &eofcount;
93 	(void) &getsub;
94 #endif
95 
96 	collf = NULL;
97 	/*
98 	 * Start catching signals from here, but we're still die on interrupts
99 	 * until we're in the main loop.
100 	 */
101 	sigemptyset(&nset);
102 	sigaddset(&nset, SIGINT);
103 	sigaddset(&nset, SIGHUP);
104 	sigprocmask(SIG_BLOCK, &nset, &oset);
105 	if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
106 		signal(SIGINT, collint);
107 	if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
108 		signal(SIGHUP, collhup);
109 	savetstp = signal(SIGTSTP, collstop);
110 	savettou = signal(SIGTTOU, collstop);
111 	savettin = signal(SIGTTIN, collstop);
112 	if (setjmp(collabort) || setjmp(colljmp)) {
113 		rm(tempMail);
114 		goto err;
115 	}
116 	sigprocmask(SIG_SETMASK, &oset, NULL);
117 
118 	noreset++;
119 	if ((collf = Fopen(tempMail, "w+")) == NULL) {
120 		perror(tempMail);
121 		goto err;
122 	}
123 	unlink(tempMail);
124 
125 	/*
126 	 * If we are going to prompt for a subject,
127 	 * refrain from printing a newline after
128 	 * the headers (since some people mind).
129 	 */
130 	t = GTO|GSUBJECT|GCC|GNL;
131 	getsub = 0;
132 	if (hp->h_subject == NOSTR && value("interactive") != NOSTR &&
133 	    (value("ask") != NOSTR || value("asksub") != NOSTR))
134 		t &= ~GNL, getsub++;
135 	if (printheaders) {
136 		puthead(hp, stdout, t);
137 		fflush(stdout);
138 	}
139 	if ((cp = value("escape")) != NOSTR)
140 		escape = *cp;
141 	else
142 		escape = ESCAPE;
143 	eofcount = 0;
144 	hadintr = 0;
145 
146 	if (!setjmp(colljmp)) {
147 		if (getsub)
148 			grabh(hp, GSUBJECT);
149 	} else {
150 		/*
151 		 * Come here for printing the after-signal message.
152 		 * Duplicate messages won't be printed because
153 		 * the write is aborted if we get a SIGTTOU.
154 		 */
155 cont:
156 		if (hadintr) {
157 			fflush(stdout);
158 			fprintf(stderr,
159 			"\n(Interrupt -- one more to kill letter)\n");
160 		} else {
161 			printf("(continue)\n");
162 			fflush(stdout);
163 		}
164 	}
165 	for (;;) {
166 		colljmp_p = 1;
167 		c = readline(stdin, linebuf, LINESIZE);
168 		colljmp_p = 0;
169 		if (c < 0) {
170 			if (value("interactive") != NOSTR &&
171 			    value("ignoreeof") != NOSTR && ++eofcount < 25) {
172 				printf("Use \".\" to terminate letter\n");
173 				continue;
174 			}
175 			break;
176 		}
177 		eofcount = 0;
178 		hadintr = 0;
179 		if (linebuf[0] == '.' && linebuf[1] == '\0' &&
180 		    value("interactive") != NOSTR &&
181 		    (value("dot") != NOSTR || value("ignoreeof") != NOSTR))
182 			break;
183 		if (linebuf[0] != escape || value("interactive") == NOSTR) {
184 			if (putline(collf, linebuf) < 0)
185 				goto err;
186 			continue;
187 		}
188 		c = linebuf[1];
189 		switch (c) {
190 		default:
191 			/*
192 			 * On double escape, just send the single one.
193 			 * Otherwise, it's an error.
194 			 */
195 			if (c == escape) {
196 				if (putline(collf, &linebuf[1]) < 0)
197 					goto err;
198 				else
199 					break;
200 			}
201 			printf("Unknown tilde escape.\n");
202 			break;
203 		case 'C':
204 			/*
205 			 * Dump core.
206 			 */
207 			core(NULL);
208 			break;
209 		case '!':
210 			/*
211 			 * Shell escape, send the balance of the
212 			 * line to sh -c.
213 			 */
214 			shell(&linebuf[2]);
215 			break;
216 		case ':':
217 		case '_':
218 			/*
219 			 * Escape to command mode, but be nice!
220 			 */
221 			execute(&linebuf[2], 1);
222 			goto cont;
223 		case '.':
224 			/*
225 			 * Simulate end of file on input.
226 			 */
227 			goto out;
228 		case 'q':
229 			/*
230 			 * Force a quit of sending mail.
231 			 * Act like an interrupt happened.
232 			 */
233 			hadintr++;
234 			collint(SIGINT);
235 			exit(1);
236 		case 'h':
237 			/*
238 			 * Grab a bunch of headers.
239 			 */
240 			grabh(hp, GTO|GSUBJECT|GCC|GBCC);
241 			goto cont;
242 		case 't':
243 			/*
244 			 * Add to the To list.
245 			 */
246 			hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
247 			break;
248 		case 's':
249 			/*
250 			 * Set the Subject list.
251 			 */
252 			cp = &linebuf[2];
253 			while (isspace(*cp))
254 				cp++;
255 			hp->h_subject = savestr(cp);
256 			break;
257 		case 'c':
258 			/*
259 			 * Add to the CC list.
260 			 */
261 			hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
262 			break;
263 		case 'b':
264 			/*
265 			 * Add stuff to blind carbon copies list.
266 			 */
267 			hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
268 			break;
269 		case 'd':
270 			strcpy(linebuf + 2, getdeadletter());
271 			/* fall into . . . */
272 		case 'r':
273 		case '<':
274 			/*
275 			 * Invoke a file:
276 			 * Search for the file name,
277 			 * then open it and copy the contents to collf.
278 			 */
279 			cp = &linebuf[2];
280 			while (isspace(*cp))
281 				cp++;
282 			if (*cp == '\0') {
283 				printf("Interpolate what file?\n");
284 				break;
285 			}
286 			cp = expand(cp);
287 			if (cp == NOSTR)
288 				break;
289 			if (isdir(cp)) {
290 				printf("%s: Directory\n", cp);
291 				break;
292 			}
293 			if ((fbuf = Fopen(cp, "r")) == NULL) {
294 				perror(cp);
295 				break;
296 			}
297 			printf("\"%s\" ", cp);
298 			fflush(stdout);
299 			lc = 0;
300 			cc = 0;
301 			while (readline(fbuf, linebuf, LINESIZE) >= 0) {
302 				lc++;
303 				if ((t = putline(collf, linebuf)) < 0) {
304 					Fclose(fbuf);
305 					goto err;
306 				}
307 				cc += t;
308 			}
309 			Fclose(fbuf);
310 			printf("%d/%d\n", lc, cc);
311 			break;
312 		case 'w':
313 			/*
314 			 * Write the message on a file.
315 			 */
316 			cp = &linebuf[2];
317 			while (*cp == ' ' || *cp == '\t')
318 				cp++;
319 			if (*cp == '\0') {
320 				fprintf(stderr, "Write what file!?\n");
321 				break;
322 			}
323 			if ((cp = expand(cp)) == NOSTR)
324 				break;
325 			rewind(collf);
326 			exwrite(cp, collf, 1);
327 			break;
328 		case 'm':
329 		case 'M':
330 		case 'f':
331 		case 'F':
332 			/*
333 			 * Interpolate the named messages, if we
334 			 * are in receiving mail mode.  Does the
335 			 * standard list processing garbage.
336 			 * If ~f is given, we don't shift over.
337 			 */
338 			if (forward(linebuf + 2, collf, c) < 0)
339 				goto err;
340 			goto cont;
341 		case '?':
342 			if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
343 				perror(_PATH_TILDE);
344 				break;
345 			}
346 			while ((t = getc(fbuf)) != EOF)
347 				(void) putchar(t);
348 			Fclose(fbuf);
349 			break;
350 		case 'p':
351 			/*
352 			 * Print out the current state of the
353 			 * message without altering anything.
354 			 */
355 			rewind(collf);
356 			printf("-------\nMessage contains:\n");
357 			puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
358 			while ((t = getc(collf)) != EOF)
359 				(void) putchar(t);
360 			goto cont;
361 		case '|':
362 			/*
363 			 * Pipe message through command.
364 			 * Collect output as new message.
365 			 */
366 			rewind(collf);
367 			mespipe(collf, &linebuf[2]);
368 			goto cont;
369 		case 'v':
370 		case 'e':
371 			/*
372 			 * Edit the current message.
373 			 * 'e' means to use EDITOR
374 			 * 'v' means to use VISUAL
375 			 */
376 			rewind(collf);
377 			mesedit(collf, c);
378 			goto cont;
379 		}
380 	}
381 	goto out;
382 err:
383 	if (collf != NULL) {
384 		Fclose(collf);
385 		collf = NULL;
386 	}
387 out:
388 	if (collf != NULL)
389 		rewind(collf);
390 	noreset--;
391 	sigemptyset(&nset);
392 	sigaddset(&nset, SIGINT);
393 	sigaddset(&nset, SIGHUP);
394 	sigprocmask(SIG_BLOCK, &nset, &oset);
395 	signal(SIGINT, saveint);
396 	signal(SIGHUP, savehup);
397 	signal(SIGTSTP, savetstp);
398 	signal(SIGTTOU, savettou);
399 	signal(SIGTTIN, savettin);
400 	sigprocmask(SIG_SETMASK, &oset, NULL);
401 	return collf;
402 }
403 
404 /*
405  * Write a file, ex-like if f set.
406  */
407 int
408 exwrite(name, fp, f)
409 	char name[];
410 	FILE *fp;
411 	int f;
412 {
413 	register FILE *of;
414 	register int c;
415 	long cc;
416 	int lc;
417 	struct stat junk;
418 
419 	if (f) {
420 		printf("\"%s\" ", name);
421 		fflush(stdout);
422 	}
423 	if (stat(name, &junk) >= 0 && (junk.st_mode & S_IFMT) == S_IFREG) {
424 		if (!f)
425 			fprintf(stderr, "%s: ", name);
426 		fprintf(stderr, "File exists\n");
427 		return(-1);
428 	}
429 	if ((of = Fopen(name, "w")) == NULL) {
430 		perror(NOSTR);
431 		return(-1);
432 	}
433 	lc = 0;
434 	cc = 0;
435 	while ((c = getc(fp)) != EOF) {
436 		cc++;
437 		if (c == '\n')
438 			lc++;
439 		(void) putc(c, of);
440 		if (ferror(of)) {
441 			perror(name);
442 			Fclose(of);
443 			return(-1);
444 		}
445 	}
446 	Fclose(of);
447 	printf("%d/%ld\n", lc, cc);
448 	fflush(stdout);
449 	return(0);
450 }
451 
452 /*
453  * Edit the message being collected on fp.
454  * On return, make the edit file the new temp file.
455  */
456 void
457 mesedit(fp, c)
458 	FILE *fp;
459 	int c;
460 {
461 	sig_t sigint = signal(SIGINT, SIG_IGN);
462 	FILE *nf = run_editor(fp, (off_t)-1, c, 0);
463 
464 	if (nf != NULL) {
465 		fseek(nf, 0L, 2);
466 		collf = nf;
467 		Fclose(fp);
468 	}
469 	(void) signal(SIGINT, sigint);
470 }
471 
472 /*
473  * Pipe the message through the command.
474  * Old message is on stdin of command;
475  * New message collected from stdout.
476  * Sh -c must return 0 to accept the new message.
477  */
478 void
479 mespipe(fp, cmd)
480 	FILE *fp;
481 	char cmd[];
482 {
483 	FILE *nf;
484 	sig_t sigint = signal(SIGINT, SIG_IGN);
485 	extern char *tempEdit;
486 	char *shell;
487 
488 	if ((nf = Fopen(tempEdit, "w+")) == NULL) {
489 		perror(tempEdit);
490 		goto out;
491 	}
492 	(void) unlink(tempEdit);
493 	/*
494 	 * stdin = current message.
495 	 * stdout = new message.
496 	 */
497 	if ((shell = value("SHELL")) == NOSTR)
498 		shell = _PATH_CSHELL;
499 	if (run_command(shell,
500 	    0, fileno(fp), fileno(nf), "-c", cmd, NOSTR) < 0) {
501 		(void) Fclose(nf);
502 		goto out;
503 	}
504 	if (fsize(nf) == 0) {
505 		fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
506 		(void) Fclose(nf);
507 		goto out;
508 	}
509 	/*
510 	 * Take new files.
511 	 */
512 	(void) fseek(nf, 0L, 2);
513 	collf = nf;
514 	(void) Fclose(fp);
515 out:
516 	(void) signal(SIGINT, sigint);
517 }
518 
519 /*
520  * Interpolate the named messages into the current
521  * message, preceding each line with a tab.
522  * Return a count of the number of characters now in
523  * the message, or -1 if an error is encountered writing
524  * the message temporary.  The flag argument is 'm' if we
525  * should shift over and 'f' if not.
526  */
527 int
528 forward(ms, fp, f)
529 	char ms[];
530 	FILE *fp;
531 	int f;
532 {
533 	register int *msgvec;
534 	extern char *tempMail;
535 	struct ignoretab *ig;
536 	char *tabst;
537 
538 	msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
539 	if (msgvec == (int *) NOSTR)
540 		return(0);
541 	if (getmsglist(ms, msgvec, 0) < 0)
542 		return(0);
543 	if (*msgvec == 0) {
544 		*msgvec = first(0, MMNORM);
545 		if (*msgvec == NULL) {
546 			printf("No appropriate messages\n");
547 			return(0);
548 		}
549 		msgvec[1] = NULL;
550 	}
551 	if (f == 'f' || f == 'F')
552 		tabst = NOSTR;
553 	else if ((tabst = value("indentprefix")) == NOSTR)
554 		tabst = "\t";
555 	ig = isupper(f) ? NULL : ignore;
556 	printf("Interpolating:");
557 	for (; *msgvec != 0; msgvec++) {
558 		struct message *mp = message + *msgvec - 1;
559 
560 		touch(mp);
561 		printf(" %d", *msgvec);
562 		if (send(mp, fp, ig, tabst) < 0) {
563 			perror(tempMail);
564 			return(-1);
565 		}
566 	}
567 	printf("\n");
568 	return(0);
569 }
570 
571 /*
572  * Print (continue) when continued after ^Z.
573  */
574 /*ARGSUSED*/
575 void
576 collstop(s)
577 	int s;
578 {
579 	sig_t old_action = signal(s, SIG_DFL);
580 	sigset_t nset;
581 
582 	sigemptyset(&nset);
583 	sigaddset(&nset, s);
584 	sigprocmask(SIG_UNBLOCK, &nset, NULL);
585 	kill(0, s);
586 	sigprocmask(SIG_BLOCK, &nset, NULL);
587 	signal(s, old_action);
588 	if (colljmp_p) {
589 		colljmp_p = 0;
590 		hadintr = 0;
591 		longjmp(colljmp, 1);
592 	}
593 }
594 
595 /*
596  * On interrupt, come here to save the partial message in ~/dead.letter.
597  * Then jump out of the collection loop.
598  */
599 /*ARGSUSED*/
600 void
601 collint(s)
602 	int s;
603 {
604 	/*
605 	 * the control flow is subtle, because we can be called from ~q.
606 	 */
607 	if (!hadintr) {
608 		if (value("ignore") != NOSTR) {
609 			puts("@");
610 			fflush(stdout);
611 			clearerr(stdin);
612 			return;
613 		}
614 		hadintr = 1;
615 		longjmp(colljmp, 1);
616 	}
617 	rewind(collf);
618 	if (value("nosave") == NOSTR)
619 		savedeadletter(collf);
620 	longjmp(collabort, 1);
621 }
622 
623 /*ARGSUSED*/
624 void
625 collhup(s)
626 	int s;
627 {
628 	rewind(collf);
629 	savedeadletter(collf);
630 	/*
631 	 * Let's pretend nobody else wants to clean up,
632 	 * a true statement at this time.
633 	 */
634 	exit(1);
635 }
636 
637 void
638 savedeadletter(fp)
639 	register FILE *fp;
640 {
641 	register FILE *dbuf;
642 	register int c;
643 	char *cp;
644 
645 	if (fsize(fp) == 0)
646 		return;
647 	cp = getdeadletter();
648 	c = umask(077);
649 	dbuf = Fopen(cp, "a");
650 	(void) umask(c);
651 	if (dbuf == NULL)
652 		return;
653 	while ((c = getc(fp)) != EOF)
654 		(void) putc(c, dbuf);
655 	Fclose(dbuf);
656 	rewind(fp);
657 }
658