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