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