xref: /openbsd-src/usr.bin/mail/send.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: send.c,v 1.21 2008/07/16 15:14:33 martynas 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.21 2008/07/16 15:14:33 martynas 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 (strcasecmp(line, "status") == 0) {
171 					/*
172 					 * If the field is "status," go compute
173 					 * and print the real Status: field
174 					 */
175 					if (dostat) {
176 						if (statusput(mp, obuf, prefix) == -1)
177 							goto out;
178 						dostat = 0;
179 					}
180 					ignoring = 1;
181 				} else {
182 					ignoring = 0;
183 					*cp2 = c;	/* restore */
184 				}
185 				infld = 1;
186 			}
187 		}
188 		if (!ignoring) {
189 			/*
190 			 * Strip trailing whitespace from prefix
191 			 * if line is blank.
192 			 */
193 			if (prefix != NULL) {
194 				if (length > 1)
195 					fputs(prefix, obuf);
196 				else
197 					(void)fwrite(prefix, sizeof(*prefix),
198 							prefixlen, obuf);
199 			}
200 			if (dovis) {
201 				length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
202 				(void)fwrite(visline, sizeof(*visline), length, obuf);
203 			} else
204 				(void)fwrite(line, sizeof(*line), length, obuf);
205 			if (ferror(obuf))
206 				goto out;
207 		}
208 		if (sendsignal == SIGINT)
209 			goto out;
210 	}
211 	/*
212 	 * Copy out message body
213 	 */
214 	if (doign == ignoreall)
215 		count--;		/* skip final blank line */
216 	while (count > 0) {
217 		if (fgets(line, sizeof(line), ibuf) == NULL) {
218 			c = 0;
219 			break;
220 		}
221 		count -= c = strlen(line);
222 		if (prefix != NULL) {
223 			/*
224 			 * Strip trailing whitespace from prefix
225 			 * if line is blank.
226 			 */
227 			if (c > 1)
228 				fputs(prefix, obuf);
229 			else
230 				(void)fwrite(prefix, sizeof(*prefix),
231 						prefixlen, obuf);
232 		}
233 		/*
234 		 * We can't read the record file (or inbox for recipient)
235 		 * properly with 'From ' lines in the message body (from
236 		 * forwarded messages or sentences starting with "From "),
237 		 * so we will prepend those lines with a '>'.
238 		 */
239 		if (strncmp(line, "From ", 5) == 0)
240 			(void)fwrite(">", 1, 1, obuf); /* '>' before 'From ' */
241 		if (dovis) {
242 			length = strvis(visline, line, VIS_SAFE|VIS_NOSLASH);
243 			(void)fwrite(visline, sizeof(*visline), length, obuf);
244 		} else
245 			(void)fwrite(line, sizeof(*line), c, obuf);
246 		if (ferror(obuf) || sendsignal == SIGINT)
247 			goto out;
248 	}
249 	if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
250 		/* no final blank line */
251 		if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
252 			goto out;
253 	rval = 0;
254 out:
255 	sendsignal = 0;
256 	(void)sigprocmask(SIG_SETMASK, &oset, NULL);
257 	(void)sigaction(SIGINT, &saveint, NULL);
258 	return(rval);
259 }
260 
261 /*
262  * Output a reasonable looking status field.
263  */
264 int
265 statusput(struct message *mp, FILE *obuf, char *prefix)
266 {
267 	char statout[3];
268 	char *cp = statout;
269 
270 	if (mp->m_flag & MREAD)
271 		*cp++ = 'R';
272 	if ((mp->m_flag & MNEW) == 0)
273 		*cp++ = 'O';
274 	*cp = 0;
275 	if (statout[0]) {
276 		fprintf(obuf, "%sStatus: %s\n",
277 			prefix == NULL ? "" : prefix, statout);
278 		return(ferror(obuf) ? -1 : 0);
279 	}
280 	return(0);
281 }
282 
283 /*
284  * Interface between the argument list and the mail1 routine
285  * which does all the dirty work.
286  */
287 int
288 mail(struct name *to, struct name *cc, struct name *bcc, struct name *smopts,
289      char *subject)
290 {
291 	struct header head;
292 
293 	head.h_to = to;
294 	head.h_subject = subject;
295 	head.h_cc = cc;
296 	head.h_bcc = bcc;
297 	head.h_smopts = smopts;
298 	mail1(&head, 0);
299 	return(0);
300 }
301 
302 
303 /*
304  * Send mail to a bunch of user names.  The interface is through
305  * the mail routine below.
306  */
307 int
308 sendmail(void *v)
309 {
310 	char *str = v;
311 	struct header head;
312 
313 	head.h_to = extract(str, GTO);
314 	head.h_subject = NULL;
315 	head.h_cc = NULL;
316 	head.h_bcc = NULL;
317 	head.h_smopts = NULL;
318 	mail1(&head, 0);
319 	return(0);
320 }
321 
322 /*
323  * Mail a message on standard input to the people indicated
324  * in the passed header.  (Internal interface).
325  */
326 void
327 mail1(struct header *hp, int printheaders)
328 {
329 	char *cp;
330 	pid_t pid;
331 	char **namelist;
332 	struct name *to;
333 	FILE *mtf;
334 
335 	/*
336 	 * Collect user's mail from standard input.
337 	 * Get the result as mtf.
338 	 */
339 	if ((mtf = collect(hp, printheaders)) == NULL)
340 		return;
341 	if (fsize(mtf) == 0) {
342 		if (value("skipempty") != NULL)
343 			goto out;
344 		if (hp->h_subject == NULL || *hp->h_subject == '\0')
345 			puts("No message, no subject; hope that's ok");
346 		else
347 			puts("Null message body; hope that's ok");
348 	}
349 	/*
350 	 * Now, take the user names from the combined
351 	 * to and cc lists and do all the alias
352 	 * processing.
353 	 */
354 	senderr = 0;
355 	to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
356 	if (to == NULL) {
357 		puts("No recipients specified");
358 		senderr++;
359 	}
360 	/*
361 	 * Look through the recipient list for names with /'s
362 	 * in them which we write to as files directly.
363 	 */
364 	to = outof(to, mtf, hp);
365 	if (senderr)
366 		savedeadletter(mtf);
367 	to = elide(to);
368 	if (count(to) == 0)
369 		goto out;
370 	fixhead(hp, to);
371 	if ((mtf = infix(hp, mtf)) == NULL) {
372 		fputs(". . . message lost, sorry.\n", stderr);
373 		return;
374 	}
375 	namelist = unpack(hp->h_smopts, to);
376 	if (debug) {
377 		char **t;
378 
379 		fputs("Sendmail arguments:", stdout);
380 		for (t = namelist; *t != NULL; t++)
381 			printf(" \"%s\"", *t);
382 		putchar('\n');
383 		goto out;
384 	}
385 	if ((cp = value("record")) != NULL)
386 		(void)savemail(expand(cp), mtf);
387 	/*
388 	 * Fork, set up the temporary mail file as standard
389 	 * input for "mail", and exec with the user list we generated
390 	 * far above.
391 	 */
392 	pid = fork();
393 	if (pid == -1) {
394 		warn("fork");
395 		savedeadletter(mtf);
396 		goto out;
397 	}
398 	if (pid == 0) {
399 		sigset_t nset;
400 
401 		sigemptyset(&nset);
402 		sigaddset(&nset, SIGHUP);
403 		sigaddset(&nset, SIGINT);
404 		sigaddset(&nset, SIGQUIT);
405 		sigaddset(&nset, SIGTSTP);
406 		sigaddset(&nset, SIGTTIN);
407 		sigaddset(&nset, SIGTTOU);
408 		prepare_child(&nset, fileno(mtf), -1);
409 		if ((cp = value("sendmail")) != NULL)
410 			cp = expand(cp);
411 		else
412 			cp = _PATH_SENDMAIL;
413 		execv(cp, namelist);
414 		warn("%s", cp);
415 		_exit(1);
416 	}
417 	if (value("verbose") != NULL)
418 		(void)wait_child(pid);
419 	else
420 		free_child(pid);
421 out:
422 	(void)Fclose(mtf);
423 }
424 
425 /*
426  * Fix the header by glopping all of the expanded names from
427  * the distribution list into the appropriate fields.
428  */
429 void
430 fixhead(struct header *hp, struct name *tolist)
431 {
432 	struct name *np;
433 
434 	hp->h_to = NULL;
435 	hp->h_cc = NULL;
436 	hp->h_bcc = NULL;
437 	for (np = tolist; np != NULL; np = np->n_flink)
438 		if ((np->n_type & GMASK) == GTO)
439 			hp->h_to =
440 				cat(hp->h_to, nalloc(np->n_name, np->n_type));
441 		else if ((np->n_type & GMASK) == GCC)
442 			hp->h_cc =
443 				cat(hp->h_cc, nalloc(np->n_name, np->n_type));
444 		else if ((np->n_type & GMASK) == GBCC)
445 			hp->h_bcc =
446 				cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
447 }
448 
449 /*
450  * Prepend a header in front of the collected stuff
451  * and return the new file.
452  */
453 FILE *
454 infix(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, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
475 	c = getc(fi);
476 	while (c != EOF) {
477 		(void)putc(c, nfo);
478 		c = getc(fi);
479 	}
480 	if (ferror(fi)) {
481 		warn("read");
482 		rewind(fi);
483 		return(fi);
484 	}
485 	(void)fflush(nfo);
486 	if (ferror(nfo)) {
487 		warn("%s", tempname);
488 		(void)Fclose(nfo);
489 		(void)Fclose(nfi);
490 		rewind(fi);
491 		return(fi);
492 	}
493 	(void)Fclose(nfo);
494 	(void)Fclose(fi);
495 	rewind(nfi);
496 	return(nfi);
497 }
498 
499 /*
500  * Dump the to, subject, cc header on the
501  * passed file buffer.
502  */
503 int
504 puthead(struct header *hp, FILE *fo, int w)
505 {
506 	int gotcha;
507 
508 	gotcha = 0;
509 	if (hp->h_to != NULL && w & GTO)
510 		fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
511 	if (hp->h_subject != NULL && w & GSUBJECT)
512 		fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
513 	if (hp->h_cc != NULL && w & GCC)
514 		fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
515 	if (hp->h_bcc != NULL && w & GBCC)
516 		fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
517 	if (gotcha && w & GNL)
518 		(void)putc('\n', fo);
519 	return(0);
520 }
521 
522 /*
523  * Format the given header line to not exceed 72 characters.
524  */
525 void
526 fmt(char *str, struct name *np, FILE *fo, int comma)
527 {
528 	int col, len;
529 
530 	comma = comma ? 1 : 0;
531 	col = strlen(str);
532 	if (col)
533 		fputs(str, fo);
534 	for (; np != NULL; np = np->n_flink) {
535 		if (np->n_flink == NULL)
536 			comma = 0;
537 		len = strlen(np->n_name);
538 		col++;		/* for the space */
539 		if (col + len + comma > 72 && col > 4) {
540 			fputs("\n    ", fo);
541 			col = 4;
542 		} else
543 			putc(' ', fo);
544 		fputs(np->n_name, fo);
545 		if (comma)
546 			putc(',', fo);
547 		col += len + comma;
548 	}
549 	putc('\n', fo);
550 }
551 
552 /*
553  * Save the outgoing mail on the passed file.
554  */
555 /*ARGSUSED*/
556 int
557 savemail(char *name, FILE *fi)
558 {
559 	FILE *fo;
560 	char buf[BUFSIZ];
561 	time_t now;
562 	mode_t m;
563 
564 	m = umask(077);
565 	fo = Fopen(name, "a");
566 	(void)umask(m);
567 	if (fo == NULL) {
568 		warn("%s", name);
569 		return(-1);
570 	}
571 	(void)time(&now);
572 	fprintf(fo, "From %s %s", myname, ctime(&now));
573 	while (fgets(buf, sizeof(buf), fi) == buf) {
574 		/*
575 		 * We can't read the record file (or inbox for recipient)
576 		 * in the message body (from forwarded messages or sentences
577 		 * starting with "From "), so we will prepend those lines with
578 		 * a '>'.
579 		 */
580 		if (strncmp(buf, "From ", 5) == 0)
581 			(void)fwrite(">", 1, 1, fo);   /* '>' before 'From ' */
582 		(void)fwrite(buf, 1, strlen(buf), fo);
583 	}
584 	(void)putc('\n', fo);
585 	(void)fflush(fo);
586 	if (ferror(fo))
587 		warn("%s", name);
588 	(void)Fclose(fo);
589 	rewind(fi);
590 	return(0);
591 }
592 
593 /*ARGSUSED*/
594 void
595 sendint(int s)
596 {
597 
598 	sendsignal = s;
599 }
600