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