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