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