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