1/*	$OpenBSD: send.c,v 1.26 2023/03/08 04:43:11 guenther Exp $	*/
2/*	$NetBSD: send.c,v 1.6 1996/06/08 19:48:39 christos 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#include "rcv.h"
34#include "extern.h"
35
36static volatile sig_atomic_t sendsignal;	/* Interrupted by a signal? */
37
38/*
39 * Mail -- a mail program
40 *
41 * Mail to others.
42 */
43
44/*
45 * Send message described by the passed pointer to the
46 * passed output buffer.  Return -1 on error.
47 * Adjust the status: field if need be.
48 * If doign is given, suppress ignored header fields.
49 * prefix is a string to prepend to each output line.
50 */
51int
52sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
53	    char *prefix)
54{
55	int count;
56	FILE *ibuf;
57	char line[LINESIZE];
58	char visline[4 * LINESIZE - 3];
59	int ishead, infld, ignoring = 0, dostat, firstline;
60	char *cp, *cp2;
61	int c = 0;
62	int length;
63	int prefixlen = 0;
64	int rval;
65	int dovis;
66	struct sigaction act, saveint;
67	sigset_t oset;
68
69	sendsignal = 0;
70	rval = -1;
71	dovis = isatty(fileno(obuf));
72	sigemptyset(&act.sa_mask);
73	act.sa_flags = SA_RESTART;
74	act.sa_handler = sendint;
75	(void)sigaction(SIGINT, &act, &saveint);
76	(void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
77
78	/*
79	 * Compute the prefix string, without trailing whitespace
80	 */
81	if (prefix != NULL) {
82		cp2 = 0;
83		for (cp = prefix; *cp; cp++)
84			if (*cp != ' ' && *cp != '\t')
85				cp2 = cp;
86		prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
87	}
88	ibuf = setinput(mp);
89	count = mp->m_size;
90	ishead = 1;
91	dostat = doign == 0 || !isign("status", doign);
92	infld = 0;
93	firstline = 1;
94	/*
95	 * Process headers first
96	 */
97	while (count > 0 && ishead) {
98		if (fgets(line, sizeof(line), ibuf) == NULL)
99			break;
100		count -= length = strlen(line);
101		if (firstline) {
102			/*
103			 * First line is the From line, so no headers
104			 * there to worry about
105			 */
106			firstline = 0;
107			ignoring = doign == ignoreall;
108		} else if (line[0] == '\n') {
109			/*
110			 * If line is blank, we've reached end of
111			 * headers, so force out status: field
112			 * and note that we are no longer in header
113			 * fields
114			 */
115			if (dostat) {
116				if (statusput(mp, obuf, prefix) == -1)
117					goto out;
118				dostat = 0;
119			}
120			ishead = 0;
121			ignoring = doign == ignoreall;
122		} else if (infld && (line[0] == ' ' || line[0] == '\t')) {
123			/*
124			 * If this line is a continuation (via space or tab)
125			 * of a previous header field, just echo it
126			 * (unless the field should be ignored).
127			 * In other words, nothing to do.
128			 */
129		} else {
130			/*
131			 * Pick up the header field if we have one.
132			 */
133			for (cp = line;
134			    (c = (unsigned char)*cp++) && c != ':' && !isspace(c); )
135				;
136			cp2 = --cp;
137			while (isspace((unsigned char)*cp++))
138				;
139			if (cp[-1] != ':') {
140				/*
141				 * Not a header line, force out status:
142				 * This happens in uucp style mail where
143				 * there are no headers at all.
144				 */
145				if (dostat) {
146					if (statusput(mp, obuf, prefix) == -1)
147						goto out;
148					dostat = 0;
149				}
150				if (doign != ignoreall)
151					/* add blank line */
152					(void)putc('\n', obuf);
153				ishead = 0;
154				ignoring = 0;
155			} else {
156				/*
157				 * If it is an ignored field and
158				 * we care about such things, skip it.
159				 */
160				*cp2 = 0;	/* temporarily null terminate */
161				if (doign && isign(line, doign))
162					ignoring = 1;
163				else if (strcasecmp(line, "status") == 0) {
164					/*
165					 * If the field is "status," go compute
166					 * and print the real Status: field
167					 */
168					if (dostat) {
169						if (statusput(mp, obuf, prefix) == -1)
170							goto out;
171						dostat = 0;
172					}
173					ignoring = 1;
174				} else {
175					ignoring = 0;
176					*cp2 = c;	/* restore */
177				}
178				infld = 1;
179			}
180		}
181		if (!ignoring) {
182			/*
183			 * Strip trailing whitespace from prefix
184			 * if line is blank.
185			 */
186			if (prefix != NULL) {
187				if (length > 1)
188					fputs(prefix, obuf);
189				else
190					(void)fwrite(prefix, sizeof(*prefix),
191							prefixlen, obuf);
192			}
193			if (dovis) {
194				length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
195				(void)fwrite(visline, sizeof(*visline), length, obuf);
196			} else
197				(void)fwrite(line, sizeof(*line), length, obuf);
198			if (ferror(obuf))
199				goto out;
200		}
201		if (sendsignal == SIGINT)
202			goto out;
203	}
204	/*
205	 * Copy out message body
206	 */
207	if (doign == ignoreall)
208		count--;		/* skip final blank line */
209	while (count > 0) {
210		if (fgets(line, sizeof(line), ibuf) == NULL) {
211			c = 0;
212			break;
213		}
214		count -= c = strlen(line);
215		if (prefix != NULL) {
216			/*
217			 * Strip trailing whitespace from prefix
218			 * if line is blank.
219			 */
220			if (c > 1)
221				fputs(prefix, obuf);
222			else
223				(void)fwrite(prefix, sizeof(*prefix),
224						prefixlen, obuf);
225		}
226		/*
227		 * We can't read the record file (or inbox for recipient)
228		 * properly with 'From ' lines in the message body (from
229		 * forwarded messages or sentences starting with "From "),
230		 * so we will prepend those lines with a '>'.
231		 */
232		if (strncmp(line, "From ", 5) == 0)
233			(void)fwrite(">", 1, 1, obuf); /* '>' before 'From ' */
234		if (dovis) {
235			length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
236			(void)fwrite(visline, sizeof(*visline), length, obuf);
237		} else
238			(void)fwrite(line, sizeof(*line), c, obuf);
239		if (ferror(obuf) || sendsignal == SIGINT)
240			goto out;
241	}
242	if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
243		/* no final blank line */
244		if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
245			goto out;
246	rval = 0;
247out:
248	sendsignal = 0;
249	(void)sigprocmask(SIG_SETMASK, &oset, NULL);
250	(void)sigaction(SIGINT, &saveint, NULL);
251	return(rval);
252}
253
254/*
255 * Output a reasonable looking status field.
256 */
257int
258statusput(struct message *mp, FILE *obuf, char *prefix)
259{
260	char statout[3];
261	char *cp = statout;
262
263	if (mp->m_flag & MREAD)
264		*cp++ = 'R';
265	if ((mp->m_flag & MNEW) == 0)
266		*cp++ = 'O';
267	*cp = 0;
268	if (statout[0]) {
269		fprintf(obuf, "%sStatus: %s\n",
270			prefix == NULL ? "" : prefix, statout);
271		return(ferror(obuf) ? -1 : 0);
272	}
273	return(0);
274}
275
276/*
277 * Interface between the argument list and the mail1 routine
278 * which does all the dirty work.
279 */
280int
281mail(struct name *to, struct name *cc, struct name *bcc, struct name *smopts,
282     char *fromaddr, char *subject)
283{
284	struct header head;
285
286	head.h_to = to;
287	head.h_from = fromaddr;
288	head.h_subject = subject;
289	head.h_cc = cc;
290	head.h_bcc = bcc;
291	head.h_smopts = smopts;
292	mail1(&head, 0);
293	return(0);
294}
295
296/*
297 * Send mail to a bunch of user names.  The interface is through
298 * the mail routine below.
299 */
300int
301sendmail(void *v)
302{
303	char *str = v;
304	struct header head;
305
306	head.h_to = extract(str, GTO);
307	head.h_from = NULL;
308	head.h_subject = NULL;
309	head.h_cc = NULL;
310	head.h_bcc = NULL;
311	head.h_smopts = NULL;
312	mail1(&head, 0);
313	return(0);
314}
315
316/*
317 * Mail a message on standard input to the people indicated
318 * in the passed header.  (Internal interface).
319 */
320void
321mail1(struct header *hp, int printheaders)
322{
323	char *cp, *envfrom = NULL;
324	char *argv[8];
325	char **ap = argv;
326	pid_t pid;
327	struct name *to;
328	FILE *mtf;
329
330	/*
331	 * Collect user's mail from standard input.
332	 * Get the result as mtf.
333	 */
334	if ((mtf = collect(hp, printheaders)) == NULL)
335		return;
336	if (fsize(mtf) == 0) {
337		if (value("skipempty") != NULL)
338			goto out;
339		if (hp->h_subject == NULL || *hp->h_subject == '\0')
340			puts("No message, no subject; hope that's ok");
341		else
342			puts("Null message body; hope that's ok");
343	}
344	/*
345	 * Now, take the user names from the combined
346	 * to and cc lists and do all the alias
347	 * processing.
348	 */
349	senderr = 0;
350	to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
351	if (to == NULL) {
352		puts("No recipients specified");
353		senderr++;
354	}
355	/*
356	 * Look through the recipient list for names with /'s
357	 * in them which we write to as files directly.
358	 */
359	to = outof(to, mtf, hp);
360	if (senderr)
361		savedeadletter(mtf);
362	to = elide(to);
363	if (count(to) == 0)
364		goto out;
365	fixhead(hp, to);
366	if ((mtf = infix(hp, mtf)) == NULL) {
367		fputs(". . . message lost, sorry.\n", stderr);
368		return;
369	}
370	if ((cp = value("record")) != NULL)
371		(void)savemail(expand(cp), mtf);
372
373	/* Setup sendmail arguments. */
374        *ap++ = "sendmail";
375        *ap++ = "-i";
376        *ap++ = "-t";
377	cp = hp->h_from ? hp->h_from : value("from");
378	if (cp != NULL) {
379		envfrom = skin(cp);
380		*ap++ = "-f";
381		*ap++ = envfrom;
382		if (envfrom == cp)
383			envfrom = NULL;
384	}
385	if (value("metoo") != NULL)
386                *ap++ = "-m";
387	if (value("verbose") != NULL)
388                *ap++ = "-v";
389	*ap = NULL;
390	if (debug) {
391		fputs("Sendmail arguments:", stdout);
392		for (ap = argv; *ap != NULL; ap++)
393			printf(" \"%s\"", *ap);
394		putchar('\n');
395		goto out;
396	}
397	/*
398	 * Fork, set up the temporary mail file as standard
399	 * input for "mail", and exec with the user list we generated
400	 * far above.
401	 */
402	pid = fork();
403	if (pid == -1) {
404		warn("fork");
405		savedeadletter(mtf);
406		goto out;
407	}
408	if (pid == 0) {
409		sigset_t nset;
410
411		sigemptyset(&nset);
412		sigaddset(&nset, SIGHUP);
413		sigaddset(&nset, SIGINT);
414		sigaddset(&nset, SIGQUIT);
415		sigaddset(&nset, SIGTSTP);
416		sigaddset(&nset, SIGTTIN);
417		sigaddset(&nset, SIGTTOU);
418		prepare_child(&nset, fileno(mtf), -1);
419		if ((cp = value("sendmail")) != NULL)
420			cp = expand(cp);
421		else
422			cp = _PATH_SENDMAIL;
423		execv(cp, argv);
424		warn("%s", cp);
425		_exit(1);
426	}
427	free(envfrom);
428	if (value("verbose") != NULL)
429		(void)wait_child(pid);
430	else
431		free_child(pid);
432out:
433	(void)Fclose(mtf);
434}
435
436/*
437 * Fix the header by glopping all of the expanded names from
438 * the distribution list into the appropriate fields.
439 */
440void
441fixhead(struct header *hp, struct name *tolist)
442{
443	struct name *np;
444
445	hp->h_to = NULL;
446	hp->h_cc = NULL;
447	hp->h_bcc = NULL;
448	for (np = tolist; np != NULL; np = np->n_flink)
449		if ((np->n_type & GMASK) == GTO)
450			hp->h_to =
451				cat(hp->h_to, nalloc(np->n_name, np->n_type));
452		else if ((np->n_type & GMASK) == GCC)
453			hp->h_cc =
454				cat(hp->h_cc, nalloc(np->n_name, np->n_type));
455		else if ((np->n_type & GMASK) == GBCC)
456			hp->h_bcc =
457				cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
458}
459
460/*
461 * Prepend a header in front of the collected stuff
462 * and return the new file.
463 */
464FILE *
465infix(struct header *hp, FILE *fi)
466{
467	FILE *nfo, *nfi;
468	int c, fd;
469	char tempname[PATHSIZE];
470
471	(void)snprintf(tempname, sizeof(tempname),
472	    "%s/mail.RsXXXXXXXXXX", tmpdir);
473	if ((fd = mkstemp(tempname)) == -1 ||
474	    (nfo = Fdopen(fd, "w")) == NULL) {
475		warn("%s", tempname);
476		return(fi);
477	}
478	if ((nfi = Fopen(tempname, "r")) == NULL) {
479		warn("%s", tempname);
480		(void)Fclose(nfo);
481		(void)rm(tempname);
482		return(fi);
483	}
484	(void)rm(tempname);
485	(void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
486	c = getc(fi);
487	while (c != EOF) {
488		(void)putc(c, nfo);
489		c = getc(fi);
490	}
491	if (ferror(fi)) {
492		warn("read");
493		rewind(fi);
494		return(fi);
495	}
496	(void)fflush(nfo);
497	if (ferror(nfo)) {
498		warn("%s", tempname);
499		(void)Fclose(nfo);
500		(void)Fclose(nfi);
501		rewind(fi);
502		return(fi);
503	}
504	(void)Fclose(nfo);
505	(void)Fclose(fi);
506	rewind(nfi);
507	return(nfi);
508}
509
510/*
511 * Dump the to, subject, cc header on the
512 * passed file buffer.
513 */
514int
515puthead(struct header *hp, FILE *fo, int w)
516{
517	int gotcha;
518	char *from;
519
520	gotcha = 0;
521	from = hp->h_from ? hp->h_from : value("from");
522	if (from != NULL)
523		fprintf(fo, "From: %s\n", from), gotcha++;
524	if (hp->h_to != NULL && w & GTO)
525		fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
526	if (hp->h_subject != NULL && w & GSUBJECT)
527		fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
528	if (hp->h_cc != NULL && w & GCC)
529		fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
530	if (hp->h_bcc != NULL && w & GBCC)
531		fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
532	if (gotcha && w & GNL)
533		(void)putc('\n', fo);
534	return(0);
535}
536
537/*
538 * Format the given header line to not exceed 72 characters.
539 */
540void
541fmt(char *str, struct name *np, FILE *fo, int comma)
542{
543	int col, len;
544
545	comma = comma ? 1 : 0;
546	col = strlen(str);
547	if (col)
548		fputs(str, fo);
549	for (; np != NULL; np = np->n_flink) {
550		if (np->n_flink == NULL)
551			comma = 0;
552		len = strlen(np->n_name);
553		col++;		/* for the space */
554		if (col + len + comma > 72 && col > 4) {
555			fputs("\n    ", fo);
556			col = 4;
557		} else
558			putc(' ', fo);
559		fputs(np->n_name, fo);
560		if (comma)
561			putc(',', fo);
562		col += len + comma;
563	}
564	putc('\n', fo);
565}
566
567/*
568 * Save the outgoing mail on the passed file.
569 */
570int
571savemail(char *name, FILE *fi)
572{
573	FILE *fo;
574	char buf[BUFSIZ];
575	time_t now;
576	mode_t m;
577
578	m = umask(077);
579	fo = Fopen(name, "a");
580	(void)umask(m);
581	if (fo == NULL) {
582		warn("%s", name);
583		return(-1);
584	}
585	(void)time(&now);
586	fprintf(fo, "From %s %s", myname, ctime(&now));
587	while (fgets(buf, sizeof(buf), fi) == buf) {
588		/*
589		 * We can't read the record file (or inbox for recipient)
590		 * in the message body (from forwarded messages or sentences
591		 * starting with "From "), so we will prepend those lines with
592		 * a '>'.
593		 */
594		if (strncmp(buf, "From ", 5) == 0)
595			(void)fwrite(">", 1, 1, fo);   /* '>' before 'From ' */
596		(void)fwrite(buf, 1, strlen(buf), fo);
597	}
598	(void)putc('\n', fo);
599	(void)fflush(fo);
600	if (ferror(fo))
601		warn("%s", name);
602	(void)Fclose(fo);
603	rewind(fi);
604	return(0);
605}
606
607void
608sendint(int s)
609{
610
611	sendsignal = s;
612}
613