1/*	$NetBSD: collect.c,v 1.49 2017/11/09 20:27:50 christos Exp $	*/
2
3/*
4 * Copyright (c) 1980, 1993
5 *	The Regents of the University of California.  All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 *    may be used to endorse or promote products derived from this software
17 *    without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)collect.c	8.2 (Berkeley) 4/19/94";
36#else
37__RCSID("$NetBSD: collect.c,v 1.49 2017/11/09 20:27:50 christos Exp $");
38#endif
39#endif /* not lint */
40
41/*
42 * Mail -- a mail program
43 *
44 * Collect input from standard input, handling
45 * ~ escapes.
46 */
47
48#include <assert.h>
49#include <util.h>
50
51#include "rcv.h"
52#include "extern.h"
53#include "format.h"
54#ifdef MIME_SUPPORT
55#include "mime.h"
56#endif
57#include "sig.h"
58#include "thread.h"
59
60
61/*
62 * Read a message from standard input and return a read file to it
63 * or NULL on error.
64 */
65
66/*
67 * The following hokiness with global variables is so that on
68 * receipt of an interrupt signal, the partial message can be salted
69 * away on dead.letter.
70 */
71static	FILE	*collf;			/* File for saving away */
72static	int	hadintr;		/* Have seen one SIGINT so far */
73
74static 	jmp_buf	abort_jmpbuf;		/* To end collection with error */
75static 	jmp_buf	reset_jmpbuf;		/* To get back to work */
76static	int	reset_on_stop;		/* To do job control longjmp. */
77
78/*
79 * Write a file, ex-like if f set.
80 */
81static int
82exwrite(const char name[], FILE *fp, int f)
83{
84	FILE *of;
85	int c;
86	long cc;
87	int lc;
88	struct stat junk;
89
90	if (f) {
91		(void)printf("\"%s\" ", name);
92		(void)fflush(stdout);
93	}
94	if (stat(name, &junk) >= 0 && S_ISREG(junk.st_mode)) {
95		if (!f)
96			(void)fprintf(stderr, "%s: ", name);
97		(void)fprintf(stderr, "File exists\n");
98		return -1;
99	}
100	if ((of = Fopen(name, "we")) == NULL) {
101		warn("%s", name);
102		return -1;
103	}
104	lc = 0;
105	cc = 0;
106	while ((c = getc(fp)) != EOF) {
107		cc++;
108		if (c == '\n')
109			lc++;
110		(void)putc(c, of);
111		if (ferror(of)) {
112			warn("%s", name);
113			(void)Fclose(of);
114			return -1;
115		}
116	}
117	(void)Fclose(of);
118	(void)printf("%d/%ld\n", lc, cc);
119	(void)fflush(stdout);
120	return 0;
121}
122
123/*
124 * Edit the message being collected on fp.
125 * On return, make the edit file the new temp file.
126 */
127static void
128mesedit(FILE *fp, int c)
129{
130	struct sigaction osa;
131	sigset_t oset;
132	FILE *nf;
133
134	sig_check();
135	(void)sig_ignore(SIGINT, &osa, &oset);
136	nf = run_editor(fp, (off_t)-1, c, 0);
137	if (nf != NULL) {
138		(void)fseek(nf, 0L, 2);
139		collf = nf;
140		(void)Fclose(fp);
141	}
142	(void)sig_restore(SIGINT, &osa, &oset);
143	sig_check();
144}
145
146/*
147 * Pipe the message through the command.
148 * Old message is on stdin of command;
149 * New message collected from stdout.
150 * Sh -c must return 0 to accept the new message.
151 */
152static void
153mespipe(FILE *fp, char cmd[])
154{
155	FILE *nf;
156	struct sigaction osa;
157	sigset_t oset;
158	const char *shellcmd;
159	int fd;
160	char tempname[PATHSIZE];
161
162	sig_check();
163	(void)sig_ignore(SIGINT, &osa, &oset);
164
165	(void)snprintf(tempname, sizeof(tempname),
166	    "%s/mail.ReXXXXXXXXXX", tmpdir);
167	if ((fd = mkstemp(tempname)) == -1 ||
168	    (nf = Fdopen(fd, "wef+")) == NULL) {
169		if (fd != -1)
170			(void)close(fd);
171		warn("%s", tempname);
172		goto out;
173	}
174	(void)unlink(tempname);
175	/*
176	 * stdin = current message.
177	 * stdout = new message.
178	 */
179	if ((shellcmd = value(ENAME_SHELL)) == NULL)
180		shellcmd = _PATH_CSHELL;
181	if (run_command(shellcmd,
182	    NULL, fileno(fp), fileno(nf), "-c", cmd, NULL) < 0) {
183		(void)Fclose(nf);
184		goto out;
185	}
186	if (fsize(nf) == 0) {
187		(void)fprintf(stderr, "No bytes from \"%s\" !?\n", cmd);
188		(void)Fclose(nf);
189		goto out;
190	}
191	/*
192	 * Take new files.
193	 */
194	(void)fseek(nf, 0L, 2);
195	collf = nf;
196	(void)Fclose(fp);
197 out:
198	(void)sig_restore(SIGINT, &osa, &oset);
199	sig_check();
200}
201
202/*
203 * Interpolate the named messages into the current
204 * message, preceding each line with a tab.
205 * Return a count of the number of characters now in
206 * the message, or -1 if an error is encountered writing
207 * the message temporary.  The flag argument is 'm' if we
208 * should shift over and 'f' if not.
209 */
210static int
211interpolate(char ms[], FILE *fp, char *fn, int f)
212{
213	int *msgvec;
214	struct ignoretab *ig;
215	const char *tabst;
216#ifdef MIME_SUPPORT
217	struct mime_info *mip;
218	int retval;
219#endif
220	msgvec = salloc((get_msgCount() + 1) * sizeof(*msgvec));
221	if (msgvec == NULL)
222		return 0;
223	if (getmsglist(ms, msgvec, 0) < 0)
224		return 0;
225	if (*msgvec == 0) {
226		*msgvec = first(0, MMNORM);
227		if (*msgvec == 0) {
228			(void)printf("No appropriate messages\n");
229			return 0;
230		}
231		msgvec[1] = 0;
232	}
233	if (f == 'f' || f == 'F')
234		tabst = NULL;
235	else if ((tabst = value(ENAME_INDENTPREFIX)) == NULL)
236		tabst = "\t";
237	ig = isupper(f) ? NULL : ignore;
238	(void)printf("Interpolating:");
239	for (/*EMPTY*/; *msgvec != 0; msgvec++) {
240		struct message *mp;
241		char *fmtstr;
242
243		mp = get_message(*msgvec);
244		touch(mp);
245		(void)printf(" %d", *msgvec);
246		(void)fflush(stdout);	/* flush stdout and the above */
247
248		if (tabst && (fmtstr = value(ENAME_INDENT_PREAMBLE)) != NULL)
249			fmsgprintf(collf, fmtstr, mp);
250#ifdef MIME_SUPPORT
251		mip = NULL;
252		if (value(ENAME_MIME_DECODE_MSG)) {
253			if ((tabst == NULL && value(ENAME_MIME_DECODE_INSERT)) ||
254			    (tabst != NULL && value(ENAME_MIME_DECODE_QUOTE)))
255				mip = mime_decode_open(mp);
256		}
257		retval = mime_sendmessage(mp, fp, ig, tabst, mip);
258		mime_decode_close(mip);
259		if (retval < 0)
260#else
261		if (sendmessage(mp, fp, ig, tabst, NULL) < 0)
262#endif
263		{
264			warn("%s", fn);
265			return -1;
266		}
267		if (tabst && (fmtstr = value(ENAME_INDENT_POSTSCRIPT)) != NULL)
268			fmsgprintf(collf, fmtstr, mp);
269	}
270	(void)printf("\n");
271	return 0;
272}
273
274/*
275 * Append the contents of the file to the end of the deadletter file.
276 */
277PUBLIC void
278savedeadletter(FILE *fp)
279{
280	FILE *dbuf;
281	mode_t m;
282	int c;
283	const char *cp;
284
285	if (fsize(fp) == 0)
286		return;
287	cp = getdeadletter();
288	m = umask(077);
289	dbuf = Fopen(cp, "ae");
290	(void)umask(m);
291	if (dbuf == NULL)
292		return;
293	(void)printf("Saving message body to `%s'.\n", cp);
294	while ((c = getc(fp)) != EOF)
295		(void)putc(c, dbuf);
296	(void)Fclose(dbuf);
297	rewind(fp);
298}
299
300/*
301 * On interrupt, come here to save the partial message in ~/dead.letter.
302 * Then jump out of the collection loop.
303 */
304static void
305coll_int(int signo)
306{
307	sig_t o = signal(SIGINT, SIG_IGN);
308
309	/*
310	 * the control flow is subtle, because we can be called from ~q.
311	 */
312	if (!hadintr) {
313		if (value(ENAME_IGNORE) != NULL) {
314			(void)puts("@");
315			(void)fflush(stdout);
316			clearerr(stdin);
317			signal(SIGINT, o);
318			return;
319		}
320		hadintr = 1;
321		signal(SIGINT, o);
322		longjmp(reset_jmpbuf, signo);
323	}
324	if (collf) {
325		rewind(collf);
326		if (value(ENAME_NOSAVE) == NULL)
327			savedeadletter(collf);
328	}
329	signal(SIGINT, o);
330	longjmp(abort_jmpbuf, signo);
331}
332
333/*ARGSUSED*/
334__dead static void
335coll_hup(int signo __unused)
336{
337
338	rewind(collf);
339	savedeadletter(collf);
340	/*
341	 * Let's pretend nobody else wants to clean up,
342	 * a true statement at this time.
343	 */
344	exit(EXIT_FAILURE);
345}
346
347/*
348 * Print (continue) when continued after ^Z.
349 */
350static void
351coll_stop(int signo)
352{
353
354	if (reset_on_stop) {
355		reset_on_stop = 0;
356		hadintr = 0;
357		longjmp(reset_jmpbuf, signo);
358	}
359}
360
361PUBLIC FILE *
362collect(struct header *hp, int printheaders)
363{
364	sig_t volatile old_sigint = sig_current(SIGINT);
365	sig_t volatile old_sighup = sig_current(SIGHUP);
366	sig_t volatile old_sigtstp = sig_current(SIGTSTP);
367	sig_t volatile old_sigttin = sig_current(SIGTTIN);
368	sig_t volatile old_sigttou = sig_current(SIGTTOU);
369	FILE *fbuf;
370	int lc, cc;
371	int c, fd, t;
372	char linebuf[LINESIZE];
373	const char *cp;
374	char tempname[PATHSIZE];
375	char mailtempname[PATHSIZE];
376	int eofcount;
377	int longline;
378	int lastlong, rc;	/* So we don't make 2 or more lines
379				   out of a long input line. */
380
381	/* The following are declared volatile to avoid longjmp clobbering. */
382	char volatile getsub;
383	int volatile escape;
384
385	(void)memset(mailtempname, 0, sizeof(mailtempname));
386	collf = NULL;
387
388	if (setjmp(abort_jmpbuf) || setjmp(reset_jmpbuf)) {
389		(void)rm(mailtempname);
390		goto err;
391	}
392	sig_check();
393
394	sig_hold();
395	old_sigint  = sig_signal(SIGINT,  coll_int);
396	old_sighup  = sig_signal(SIGHUP,  coll_hup);
397	old_sigtstp = sig_signal(SIGTSTP, coll_stop);
398	old_sigttin = sig_signal(SIGTTIN, coll_stop);
399	old_sigttou = sig_signal(SIGTTOU, coll_stop);
400	sig_release();
401
402	noreset++;
403	(void)snprintf(mailtempname, sizeof(mailtempname),
404	    "%s/mail.RsXXXXXXXXXX", tmpdir);
405	if ((fd = mkstemp(mailtempname)) == -1 ||
406	    (collf = Fdopen(fd, "wef+")) == NULL) {
407		if (fd != -1)
408			(void)close(fd);
409		warn("%s", mailtempname);
410		goto err;
411	}
412	(void)rm(mailtempname);
413
414	/*
415	 * If we are going to prompt for a subject,
416	 * refrain from printing a newline after
417	 * the headers (since some people mind).
418	 */
419	t = GTO | GSUBJECT | GCC | GNL | GSMOPTS;
420	getsub = 0;
421	if (hp->h_subject == NULL && value(ENAME_INTERACTIVE) != NULL &&
422	    (value(ENAME_ASK) != NULL || value(ENAME_ASKSUB) != NULL)) {
423		t &= ~GNL;
424		getsub++;
425	}
426	if (printheaders) {
427		(void)puthead(hp, stdout, t);
428		(void)fflush(stdout);
429	}
430	if ((cp = value(ENAME_ESCAPE)) != NULL)
431		escape = *cp;
432	else
433		escape = ESCAPE;
434	hadintr = 0;
435	if (setjmp(reset_jmpbuf) == 0) {
436		if (getsub)
437			(void)grabh(hp, GSUBJECT);
438	} else {
439		/*
440		 * Come here for printing the after-signal message.
441		 * Duplicate messages won't be printed because
442		 * the write is aborted if we get a SIGTTOU.
443		 */
444 cont:
445		if (hadintr) {
446			(void)fflush(stdout);
447			(void)fprintf(stderr,
448			"\n(Interrupt -- one more to kill letter)\n");
449		} else {
450			(void)printf("(continue)\n");
451			(void)fflush(stdout);
452		}
453	}
454	eofcount = 0;	/* reset after possible longjmp */
455	longline = 0;	/* reset after possible longjmp */
456	for (;;) {
457		reset_on_stop = 1;
458		c = readline(stdin, linebuf, LINESIZE, reset_on_stop);
459		reset_on_stop = 0;
460
461		if (c < 0) {
462			char *p;
463
464			if (value(ENAME_INTERACTIVE) != NULL &&
465			    (p = value(ENAME_IGNOREEOF)) != NULL &&
466			    ++eofcount < (*p == 0 ? 25 : atoi(p))) {
467				(void)printf("Use \".\" to terminate letter\n");
468				continue;
469			}
470			break;
471		}
472		lastlong = longline;
473		longline = c == LINESIZE - 1;
474		eofcount = 0;
475		hadintr = 0;
476		if (linebuf[0] == '.' && linebuf[1] == '\0' &&
477		    value(ENAME_INTERACTIVE) != NULL && !lastlong &&
478		    (value(ENAME_DOT) != NULL || value(ENAME_IGNOREEOF) != NULL))
479			break;
480		if (linebuf[0] != escape || value(ENAME_INTERACTIVE) == NULL ||
481		    lastlong) {
482			if (putline(collf, linebuf, !longline) < 0)
483				goto err;
484			continue;
485		}
486		c = linebuf[1];
487		switch (c) {
488		default:
489			/*
490			 * On double escape, just send the single one.
491			 * Otherwise, it's an error.
492			 */
493			if (c == escape) {
494				if (putline(collf, &linebuf[1], !longline) < 0)
495					goto err;
496				else
497					break;
498			}
499			(void)printf("Unknown tilde escape.\n");
500			break;
501#ifdef MIME_SUPPORT
502		case '@':
503			hp->h_attach = mime_attach_files(hp->h_attach, &linebuf[2]);
504			break;
505#endif
506		case 'C':
507			/*
508			 * Dump core.
509			 */
510			(void)core(NULL);
511			break;
512		case '!':
513			/*
514			 * Shell escape, send the balance of the
515			 * line to sh -c.
516			 */
517			(void)shell(&linebuf[2]);
518			break;
519		case ':':
520		case '_':
521			/*
522			 * Escape to command mode, but be nice!
523			 */
524			(void)execute(&linebuf[2], ec_composing);
525			goto cont;
526		case '.':
527			/*
528			 * Simulate end of file on input.
529			 */
530			goto out;
531		case 'q':
532			/*
533			 * Force a quit of sending mail.
534			 * Act like an interrupt happened.
535			 */
536			hadintr++;
537			coll_int(SIGINT);
538			exit(1);
539			/*NOTREACHED*/
540
541		case 'x':	/* exit, do not save in dead.letter */
542			goto err;
543
544		case 'h':
545			/*
546			 * Grab a bunch of headers.
547			 */
548			(void)grabh(hp, GTO | GSUBJECT | GCC | GBCC | GSMOPTS);
549			goto cont;
550		case 't':
551			/*
552			 * Add to the To list.
553			 */
554			hp->h_to = cat(hp->h_to, extract(&linebuf[2], GTO));
555			break;
556		case 's':
557			/*
558			 * Set the Subject list.
559			 */
560			cp = skip_WSP(&linebuf[2]);
561			hp->h_subject = savestr(cp);
562			break;
563		case 'c':
564			/*
565			 * Add to the CC list.
566			 */
567			hp->h_cc = cat(hp->h_cc, extract(&linebuf[2], GCC));
568			break;
569		case 'b':
570			/*
571			 * Add stuff to blind carbon copies list.
572			 */
573			hp->h_bcc = cat(hp->h_bcc, extract(&linebuf[2], GBCC));
574			break;
575		case 'i':
576		case 'A':
577		case 'a':
578			/*
579			 * Insert named variable in message
580			 */
581
582			switch(c) {
583				case 'i':
584					cp = skip_WSP(&linebuf[2]);
585					break;
586				case 'a':
587					cp = "sign";
588					break;
589				case 'A':
590					cp = "Sign";
591					break;
592				default:
593					goto err;
594			}
595
596			if (*cp && (cp = value(cp)) != NULL) {
597				(void)printf("%s\n", cp);
598				if (putline(collf, cp, 1) < 0)
599					goto err;
600			}
601
602			break;
603
604		case 'd':
605			(void)strcpy(linebuf + 2, getdeadletter());
606			/* FALLTHROUGH */
607		case 'r':
608		case '<':
609			/*
610			 * Invoke a file:
611			 * Search for the file name,
612			 * then open it and copy the contents to collf.
613			 */
614			cp = skip_WSP(&linebuf[2]);
615			if (*cp == '\0') {
616				(void)printf("Interpolate what file?\n");
617				break;
618			}
619
620			cp = expand(cp);
621			if (cp == NULL)
622				break;
623
624			if (*cp == '!') {	/* insert stdout of command */
625				const char *shellcmd;
626				int nullfd;
627				int rc2;
628
629				if ((nullfd = open("/dev/null", O_RDONLY, 0)) == -1) {
630					warn("/dev/null");
631					break;
632				}
633
634				(void)snprintf(tempname, sizeof(tempname),
635				    "%s/mail.ReXXXXXXXXXX", tmpdir);
636				if ((fd = mkstemp(tempname)) == -1 ||
637				    (fbuf = Fdopen(fd, "wef+")) == NULL) {
638					if (fd != -1)
639						(void)close(fd);
640					warn("%s", tempname);
641					break;
642				}
643				(void)unlink(tempname);
644
645				if ((shellcmd = value(ENAME_SHELL)) == NULL)
646					shellcmd = _PATH_CSHELL;
647
648				rc2 = run_command(shellcmd, NULL, nullfd, fileno(fbuf), "-c", cp + 1, NULL);
649
650				(void)close(nullfd);
651
652				if (rc2 < 0) {
653					(void)Fclose(fbuf);
654					break;
655				}
656
657				if (fsize(fbuf) == 0) {
658					(void)fprintf(stderr, "No bytes from command \"%s\"\n", cp + 1);
659					(void)Fclose(fbuf);
660					break;
661				}
662
663				rewind(fbuf);
664			}
665			else if (isdir(cp)) {
666				(void)printf("%s: Directory\n", cp);
667				break;
668			}
669			else if ((fbuf = Fopen(cp, "re")) == NULL) {
670				warn("%s", cp);
671				break;
672			}
673			(void)printf("\"%s\" ", cp);
674			(void)fflush(stdout);
675			lc = 0;
676			cc = 0;
677			while ((rc = readline(fbuf, linebuf, LINESIZE, 0)) >= 0) {
678				if (rc != LINESIZE-1) lc++;
679				if ((t = putline(collf, linebuf,
680						 rc != LINESIZE-1)) < 0) {
681					(void)Fclose(fbuf);
682					goto err;
683				}
684				cc += t;
685			}
686			(void)Fclose(fbuf);
687			(void)printf("%d/%d\n", lc, cc);
688			break;
689		case 'w':
690			/*
691			 * Write the message on a file.
692			 */
693			cp = skip_WSP(&linebuf[2]);
694			if (*cp == '\0') {
695				(void)fprintf(stderr, "Write what file!?\n");
696				break;
697			}
698			if ((cp = expand(cp)) == NULL)
699				break;
700			rewind(collf);
701			(void)exwrite(cp, collf, 1);
702			break;
703		case 'm':
704		case 'M':
705		case 'f':
706		case 'F':
707			/*
708			 * Interpolate the named messages, if we
709			 * are in receiving mail mode.  Does the
710			 * standard list processing garbage.
711			 * If ~f is given, we don't shift over.
712			 */
713			if (interpolate(linebuf + 2, collf, mailtempname, c) < 0)
714				goto err;
715			goto cont;
716		case '?':
717			cathelp(_PATH_TILDE);
718			break;
719		case 'p':
720			/*
721			 * Print out the current state of the
722			 * message without altering anything.
723			 */
724			rewind(collf);
725			(void)printf("-------\nMessage contains:\n");
726			(void)puthead(hp, stdout,
727			    GTO | GSUBJECT | GCC | GBCC | GSMOPTS | GNL);
728			while ((t = getc(collf)) != EOF)
729				(void)putchar(t);
730			goto cont;
731		case '|':
732			/*
733			 * Pipe message through command.
734			 * Collect output as new message.
735			 */
736			rewind(collf);
737			mespipe(collf, &linebuf[2]);
738			goto cont;
739		case 'v':
740		case 'e':
741			/*
742			 * Edit the current message.
743			 * 'e' means to use EDITOR
744			 * 'v' means to use VISUAL
745			 */
746			rewind(collf);
747			mesedit(collf, c);
748			goto cont;
749		}
750	}
751	goto out;
752 err:
753	if (collf != NULL) {
754		(void)Fclose(collf);
755		collf = NULL;
756	}
757 out:
758	if (collf != NULL)
759		rewind(collf);
760	noreset--;
761
762	sig_hold();
763	(void)sig_signal(SIGINT,  old_sigint);
764	(void)sig_signal(SIGHUP,  old_sighup);
765	(void)sig_signal(SIGTSTP, old_sigtstp);
766	(void)sig_signal(SIGTTIN, old_sigttin);
767	(void)sig_signal(SIGTTOU, old_sigttou);
768	sig_release();
769
770	sig_check();
771	return collf;
772}
773