xref: /openbsd/usr.bin/mail/send.c (revision 5b133f3f)
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 
36 static 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  */
51 int
sendmessage(struct message * mp,FILE * obuf,struct ignoretab * doign,char * prefix)52 sendmessage(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;
247 out:
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  */
257 int
statusput(struct message * mp,FILE * obuf,char * prefix)258 statusput(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  */
280 int
mail(struct name * to,struct name * cc,struct name * bcc,struct name * smopts,char * fromaddr,char * subject)281 mail(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  */
300 int
sendmail(void * v)301 sendmail(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  */
320 void
mail1(struct header * hp,int printheaders)321 mail1(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);
432 out:
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  */
440 void
fixhead(struct header * hp,struct name * tolist)441 fixhead(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  */
464 FILE *
infix(struct header * hp,FILE * fi)465 infix(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  */
514 int
puthead(struct header * hp,FILE * fo,int w)515 puthead(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  */
540 void
fmt(char * str,struct name * np,FILE * fo,int comma)541 fmt(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  */
570 int
savemail(char * name,FILE * fi)571 savemail(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 
607 void
sendint(int s)608 sendint(int s)
609 {
610 
611 	sendsignal = s;
612 }
613