xref: /original-bsd/usr.bin/mail/collect.c (revision e58c8952)
1 /*
2  * Copyright (c) 1980, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)collect.c	8.2 (Berkeley) 04/19/94";
10 #endif /* not lint */
11 
12 /*
13  * Mail -- a mail program
14  *
15  * Collect input from standard input, handling
16  * ~ escapes.
17  */
18 
19 #include "rcv.h"
20 #include "extern.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	sig_t	saveint;		/* Previous SIGINT value */
34 static	sig_t	savehup;		/* Previous SIGHUP value */
35 static	sig_t	savetstp;		/* Previous SIGTSTP value */
36 static	sig_t	savettou;		/* Previous SIGTTOU value */
37 static	sig_t	savettin;		/* Previous SIGTTIN value */
38 static	FILE	*collf;			/* File for saving away */
39 static	int	hadintr;		/* Have seen one SIGINT so far */
40 
41 static	jmp_buf	colljmp;		/* To get back to work */
42 static	int	colljmp_p;		/* whether to long jump */
43 static	jmp_buf	collabort;		/* To end collection with error */
44 
45 FILE *
46 collect(hp, printheaders)
47 	struct header *hp;
48 	int printheaders;
49 {
50 	FILE *fbuf;
51 	int lc, cc, escape, eofcount;
52 	register int c, t;
53 	char linebuf[LINESIZE], *cp;
54 	extern char tempMail[];
55 	char getsub;
56 	int omask;
57 	void collint(), collhup(), collstop();
58 
59 	collf = NULL;
60 	/*
61 	 * Start catching signals from here, but we're still die on interrupts
62 	 * until we're in the main loop.
63 	 */
64 	omask = sigblock(sigmask(SIGINT) | sigmask(SIGHUP));
65 	if ((saveint = signal(SIGINT, SIG_IGN)) != SIG_IGN)
66 		signal(SIGINT, collint);
67 	if ((savehup = signal(SIGHUP, SIG_IGN)) != SIG_IGN)
68 		signal(SIGHUP, collhup);
69 	savetstp = signal(SIGTSTP, collstop);
70 	savettou = signal(SIGTTOU, collstop);
71 	savettin = signal(SIGTTIN, collstop);
72 	if (setjmp(collabort) || setjmp(colljmp)) {
73 		rm(tempMail);
74 		goto err;
75 	}
76 	sigsetmask(omask & ~(sigmask(SIGINT) | sigmask(SIGHUP)));
77 
78 	noreset++;
79 	if ((collf = Fopen(tempMail, "w+")) == NULL) {
80 		perror(tempMail);
81 		goto err;
82 	}
83 	unlink(tempMail);
84 
85 	/*
86 	 * If we are going to prompt for a subject,
87 	 * refrain from printing a newline after
88 	 * the headers (since some people mind).
89 	 */
90 	t = GTO|GSUBJECT|GCC|GNL;
91 	getsub = 0;
92 	if (hp->h_subject == NOSTR && value("interactive") != NOSTR &&
93 	    (value("ask") != NOSTR || value("asksub") != NOSTR))
94 		t &= ~GNL, getsub++;
95 	if (printheaders) {
96 		puthead(hp, stdout, t);
97 		fflush(stdout);
98 	}
99 	if ((cp = value("escape")) != NOSTR)
100 		escape = *cp;
101 	else
102 		escape = ESCAPE;
103 	eofcount = 0;
104 	hadintr = 0;
105 
106 	if (!setjmp(colljmp)) {
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 		colljmp_p = 1;
127 		c = readline(stdin, linebuf, LINESIZE);
128 		colljmp_p = 0;
129 		if (c < 0) {
130 			if (value("interactive") != NOSTR &&
131 			    value("ignoreeof") != NOSTR && ++eofcount < 25) {
132 				printf("Use \".\" to terminate letter\n");
133 				continue;
134 			}
135 			break;
136 		}
137 		eofcount = 0;
138 		hadintr = 0;
139 		if (linebuf[0] == '.' && linebuf[1] == '\0' &&
140 		    value("interactive") != NOSTR &&
141 		    (value("dot") != NOSTR || value("ignoreeof") != NOSTR))
142 			break;
143 		if (linebuf[0] != escape || value("interactive") == NOSTR) {
144 			if (putline(collf, linebuf) < 0)
145 				goto err;
146 			continue;
147 		}
148 		c = linebuf[1];
149 		switch (c) {
150 		default:
151 			/*
152 			 * On double escape, just send the single one.
153 			 * Otherwise, it's an error.
154 			 */
155 			if (c == escape) {
156 				if (putline(collf, &linebuf[1]) < 0)
157 					goto err;
158 				else
159 					break;
160 			}
161 			printf("Unknown tilde escape.\n");
162 			break;
163 		case 'C':
164 			/*
165 			 * Dump core.
166 			 */
167 			core();
168 			break;
169 		case '!':
170 			/*
171 			 * Shell escape, send the balance of the
172 			 * line to sh -c.
173 			 */
174 			shell(&linebuf[2]);
175 			break;
176 		case ':':
177 			/*
178 			 * Escape to command mode, but be nice!
179 			 */
180 			execute(&linebuf[2], 1);
181 			goto cont;
182 		case '.':
183 			/*
184 			 * Simulate end of file on input.
185 			 */
186 			goto out;
187 		case 'q':
188 			/*
189 			 * Force a quit of sending mail.
190 			 * Act like an interrupt happened.
191 			 */
192 			hadintr++;
193 			collint(SIGINT);
194 			exit(1);
195 		case 'h':
196 			/*
197 			 * Grab a bunch of headers.
198 			 */
199 			grabh(hp, GTO|GSUBJECT|GCC|GBCC);
200 			goto cont;
201 		case 't':
202 			/*
203 			 * Add to the To list.
204 			 */
205 			hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
206 			break;
207 		case 's':
208 			/*
209 			 * Set the Subject list.
210 			 */
211 			cp = &linebuf[2];
212 			while (isspace(*cp))
213 				cp++;
214 			hp->h_subject = savestr(cp);
215 			break;
216 		case 'c':
217 			/*
218 			 * Add to the CC list.
219 			 */
220 			hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
221 			break;
222 		case 'b':
223 			/*
224 			 * Add stuff to blind carbon copies list.
225 			 */
226 			hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
227 			break;
228 		case 'd':
229 			strcpy(linebuf + 2, getdeadletter());
230 			/* fall into . . . */
231 		case 'r':
232 			/*
233 			 * Invoke a file:
234 			 * Search for the file name,
235 			 * then open it and copy the contents to collf.
236 			 */
237 			cp = &linebuf[2];
238 			while (isspace(*cp))
239 				cp++;
240 			if (*cp == '\0') {
241 				printf("Interpolate what file?\n");
242 				break;
243 			}
244 			cp = expand(cp);
245 			if (cp == NOSTR)
246 				break;
247 			if (isdir(cp)) {
248 				printf("%s: Directory\n", cp);
249 				break;
250 			}
251 			if ((fbuf = Fopen(cp, "r")) == NULL) {
252 				perror(cp);
253 				break;
254 			}
255 			printf("\"%s\" ", cp);
256 			fflush(stdout);
257 			lc = 0;
258 			cc = 0;
259 			while (readline(fbuf, linebuf, LINESIZE) >= 0) {
260 				lc++;
261 				if ((t = putline(collf, linebuf)) < 0) {
262 					Fclose(fbuf);
263 					goto err;
264 				}
265 				cc += t;
266 			}
267 			Fclose(fbuf);
268 			printf("%d/%d\n", lc, cc);
269 			break;
270 		case 'w':
271 			/*
272 			 * Write the message on a file.
273 			 */
274 			cp = &linebuf[2];
275 			while (*cp == ' ' || *cp == '\t')
276 				cp++;
277 			if (*cp == '\0') {
278 				fprintf(stderr, "Write what file!?\n");
279 				break;
280 			}
281 			if ((cp = expand(cp)) == NOSTR)
282 				break;
283 			rewind(collf);
284 			exwrite(cp, collf, 1);
285 			break;
286 		case 'm':
287 		case 'M':
288 		case 'f':
289 		case 'F':
290 			/*
291 			 * Interpolate the named messages, if we
292 			 * are in receiving mail mode.  Does the
293 			 * standard list processing garbage.
294 			 * If ~f is given, we don't shift over.
295 			 */
296 			if (forward(linebuf + 2, collf, c) < 0)
297 				goto err;
298 			goto cont;
299 		case '?':
300 			if ((fbuf = Fopen(_PATH_TILDE, "r")) == NULL) {
301 				perror(_PATH_TILDE);
302 				break;
303 			}
304 			while ((t = getc(fbuf)) != EOF)
305 				(void) putchar(t);
306 			Fclose(fbuf);
307 			break;
308 		case 'p':
309 			/*
310 			 * Print out the current state of the
311 			 * message without altering anything.
312 			 */
313 			rewind(collf);
314 			printf("-------\nMessage contains:\n");
315 			puthead(hp, stdout, GTO|GSUBJECT|GCC|GBCC|GNL);
316 			while ((t = getc(collf)) != EOF)
317 				(void) putchar(t);
318 			goto cont;
319 		case '|':
320 			/*
321 			 * Pipe message through command.
322 			 * Collect output as new message.
323 			 */
324 			rewind(collf);
325 			mespipe(collf, &linebuf[2]);
326 			goto cont;
327 		case 'v':
328 		case 'e':
329 			/*
330 			 * Edit the current message.
331 			 * 'e' means to use EDITOR
332 			 * 'v' means to use VISUAL
333 			 */
334 			rewind(collf);
335 			mesedit(collf, c);
336 			goto cont;
337 		}
338 	}
339 	goto out;
340 err:
341 	if (collf != NULL) {
342 		Fclose(collf);
343 		collf = NULL;
344 	}
345 out:
346 	if (collf != NULL)
347 		rewind(collf);
348 	noreset--;
349 	sigblock(sigmask(SIGINT) | sigmask(SIGHUP));
350 	signal(SIGINT, saveint);
351 	signal(SIGHUP, savehup);
352 	signal(SIGTSTP, savetstp);
353 	signal(SIGTTOU, savettou);
354 	signal(SIGTTIN, savettin);
355 	sigsetmask(omask);
356 	return collf;
357 }
358 
359 /*
360  * Write a file, ex-like if f set.
361  */
362 int
363 exwrite(name, fp, f)
364 	char name[];
365 	FILE *fp;
366 	int f;
367 {
368 	register FILE *of;
369 	register int c;
370 	long cc;
371 	int lc;
372 	struct stat junk;
373 
374 	if (f) {
375 		printf("\"%s\" ", name);
376 		fflush(stdout);
377 	}
378 	if (stat(name, &junk) >= 0 && (junk.st_mode & S_IFMT) == S_IFREG) {
379 		if (!f)
380 			fprintf(stderr, "%s: ", name);
381 		fprintf(stderr, "File exists\n");
382 		return(-1);
383 	}
384 	if ((of = Fopen(name, "w")) == NULL) {
385 		perror(NOSTR);
386 		return(-1);
387 	}
388 	lc = 0;
389 	cc = 0;
390 	while ((c = getc(fp)) != EOF) {
391 		cc++;
392 		if (c == '\n')
393 			lc++;
394 		(void) putc(c, of);
395 		if (ferror(of)) {
396 			perror(name);
397 			Fclose(of);
398 			return(-1);
399 		}
400 	}
401 	Fclose(of);
402 	printf("%d/%ld\n", lc, cc);
403 	fflush(stdout);
404 	return(0);
405 }
406 
407 /*
408  * Edit the message being collected on fp.
409  * On return, make the edit file the new temp file.
410  */
411 void
412 mesedit(fp, c)
413 	FILE *fp;
414 	int c;
415 {
416 	sig_t sigint = signal(SIGINT, SIG_IGN);
417 	FILE *nf = run_editor(fp, (off_t)-1, c, 0);
418 
419 	if (nf != NULL) {
420 		fseek(nf, 0L, 2);
421 		collf = nf;
422 		Fclose(fp);
423 	}
424 	(void) signal(SIGINT, sigint);
425 }
426 
427 /*
428  * Pipe the message through the command.
429  * Old message is on stdin of command;
430  * New message collected from stdout.
431  * Sh -c must return 0 to accept the new message.
432  */
433 void
434 mespipe(fp, cmd)
435 	FILE *fp;
436 	char cmd[];
437 {
438 	FILE *nf;
439 	sig_t sigint = signal(SIGINT, SIG_IGN);
440 	extern char tempEdit[];
441 	char *shell;
442 
443 	if ((nf = Fopen(tempEdit, "w+")) == NULL) {
444 		perror(tempEdit);
445 		goto out;
446 	}
447 	(void) unlink(tempEdit);
448 	/*
449 	 * stdin = current message.
450 	 * stdout = new message.
451 	 */
452 	if ((shell = value("SHELL")) == NOSTR)
453 		shell = _PATH_CSHELL;
454 	if (run_command(shell,
455 	    0, fileno(fp), fileno(nf), "-c", cmd, NOSTR) < 0) {
456 		(void) Fclose(nf);
457 		goto out;
458 	}
459 	if (fsize(nf) == 0) {
460 		fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
461 		(void) Fclose(nf);
462 		goto out;
463 	}
464 	/*
465 	 * Take new files.
466 	 */
467 	(void) fseek(nf, 0L, 2);
468 	collf = nf;
469 	(void) Fclose(fp);
470 out:
471 	(void) signal(SIGINT, sigint);
472 }
473 
474 /*
475  * Interpolate the named messages into the current
476  * message, preceding each line with a tab.
477  * Return a count of the number of characters now in
478  * the message, or -1 if an error is encountered writing
479  * the message temporary.  The flag argument is 'm' if we
480  * should shift over and 'f' if not.
481  */
482 int
483 forward(ms, fp, f)
484 	char ms[];
485 	FILE *fp;
486 	int f;
487 {
488 	register int *msgvec;
489 	extern char tempMail[];
490 	struct ignoretab *ig;
491 	char *tabst;
492 
493 	msgvec = (int *) salloc((msgCount+1) * sizeof *msgvec);
494 	if (msgvec == (int *) NOSTR)
495 		return(0);
496 	if (getmsglist(ms, msgvec, 0) < 0)
497 		return(0);
498 	if (*msgvec == 0) {
499 		*msgvec = first(0, MMNORM);
500 		if (*msgvec == NULL) {
501 			printf("No appropriate messages\n");
502 			return(0);
503 		}
504 		msgvec[1] = NULL;
505 	}
506 	if (f == 'f' || f == 'F')
507 		tabst = NOSTR;
508 	else if ((tabst = value("indentprefix")) == NOSTR)
509 		tabst = "\t";
510 	ig = isupper(f) ? NULL : ignore;
511 	printf("Interpolating:");
512 	for (; *msgvec != 0; msgvec++) {
513 		struct message *mp = message + *msgvec - 1;
514 
515 		touch(mp);
516 		printf(" %d", *msgvec);
517 		if (send(mp, fp, ig, tabst) < 0) {
518 			perror(tempMail);
519 			return(-1);
520 		}
521 	}
522 	printf("\n");
523 	return(0);
524 }
525 
526 /*
527  * Print (continue) when continued after ^Z.
528  */
529 /*ARGSUSED*/
530 void
531 collstop(s)
532 	int s;
533 {
534 	sig_t old_action = signal(s, SIG_DFL);
535 
536 	sigsetmask(sigblock(0) & ~sigmask(s));
537 	kill(0, s);
538 	sigblock(sigmask(s));
539 	signal(s, old_action);
540 	if (colljmp_p) {
541 		colljmp_p = 0;
542 		hadintr = 0;
543 		longjmp(colljmp, 1);
544 	}
545 }
546 
547 /*
548  * On interrupt, come here to save the partial message in ~/dead.letter.
549  * Then jump out of the collection loop.
550  */
551 /*ARGSUSED*/
552 void
553 collint(s)
554 	int s;
555 {
556 	/*
557 	 * the control flow is subtle, because we can be called from ~q.
558 	 */
559 	if (!hadintr) {
560 		if (value("ignore") != NOSTR) {
561 			puts("@");
562 			fflush(stdout);
563 			clearerr(stdin);
564 			return;
565 		}
566 		hadintr = 1;
567 		longjmp(colljmp, 1);
568 	}
569 	rewind(collf);
570 	if (value("nosave") == NOSTR)
571 		savedeadletter(collf);
572 	longjmp(collabort, 1);
573 }
574 
575 /*ARGSUSED*/
576 void
577 collhup(s)
578 	int s;
579 {
580 	rewind(collf);
581 	savedeadletter(collf);
582 	/*
583 	 * Let's pretend nobody else wants to clean up,
584 	 * a true statement at this time.
585 	 */
586 	exit(1);
587 }
588 
589 void
590 savedeadletter(fp)
591 	register FILE *fp;
592 {
593 	register FILE *dbuf;
594 	register int c;
595 	char *cp;
596 
597 	if (fsize(fp) == 0)
598 		return;
599 	cp = getdeadletter();
600 	c = umask(077);
601 	dbuf = Fopen(cp, "a");
602 	(void) umask(c);
603 	if (dbuf == NULL)
604 		return;
605 	while ((c = getc(fp)) != EOF)
606 		(void) putc(c, dbuf);
607 	Fclose(dbuf);
608 	rewind(fp);
609 }
610