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