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