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