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