xref: /freebsd/usr.bin/mail/send.c (revision 640235e2c2ba32947f7c59d168437ffa1280f1e6)
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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)send.c	8.1 (Berkeley) 6/6/93";
33 #endif
34 #endif /* not lint */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
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 	char *nbuf;
293 	int pid;
294 	char **namelist;
295 	struct name *to, *nsto;
296 	FILE *mtf;
297 
298 	/*
299 	 * Collect user's mail from standard input.
300 	 * Get the result as mtf.
301 	 */
302 	if ((mtf = collect(hp, printheaders)) == NULL)
303 		return;
304 	if (value("interactive") != NULL) {
305 		if (value("askcc") != NULL || value("askbcc") != NULL) {
306 			if (value("askcc") != NULL)
307 				grabh(hp, GCC);
308 			if (value("askbcc") != NULL)
309 				grabh(hp, GBCC);
310 		} else {
311 			printf("EOT\n");
312 			(void)fflush(stdout);
313 		}
314 	}
315 	if (fsize(mtf) == 0) {
316 		if (value("dontsendempty") != NULL)
317 			goto out;
318 		if (hp->h_subject == NULL)
319 			printf("No message, no subject; hope that's ok\n");
320 		else
321 			printf("Null message body; hope that's ok\n");
322 	}
323 	/*
324 	 * Now, take the user names from the combined
325 	 * to and cc lists and do all the alias
326 	 * processing.
327 	 */
328 	senderr = 0;
329 	to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
330 	if (to == NULL) {
331 		printf("No recipients specified\n");
332 		senderr++;
333 	}
334 	/*
335 	 * Look through the recipient list for names with /'s
336 	 * in them which we write to as files directly.
337 	 */
338 	to = outof(to, mtf, hp);
339 	if (senderr)
340 		savedeadletter(mtf);
341 	to = elide(to);
342 	if (count(to) == 0)
343 		goto out;
344 	if (value("recordrecip") != NULL) {
345 		/*
346 		 * Before fixing the header, save old To:.
347 		 * We do this because elide above has sorted To: list, and
348 		 * we would like to save message in a file named by the first
349 		 * recipient the user has entered, not the one being the first
350 		 * after sorting happened.
351 		 */
352 		if ((nsto = malloc(sizeof(struct name))) == NULL)
353 			err(1, "Out of memory");
354 		bcopy(hp->h_to, nsto, sizeof(struct name));
355 	}
356 	fixhead(hp, to);
357 	if ((mtf = infix(hp, mtf)) == NULL) {
358 		fprintf(stderr, ". . . message lost, sorry.\n");
359 		return;
360 	}
361 	namelist = unpack(cat(hp->h_smopts, to));
362 	if (debug) {
363 		char **t;
364 
365 		printf("Sendmail arguments:");
366 		for (t = namelist; *t != NULL; t++)
367 			printf(" \"%s\"", *t);
368 		printf("\n");
369 		goto out;
370 	}
371 	if (value("recordrecip") != NULL) {
372 		/*
373 		 * Extract first recipient username from saved To: and use it
374 		 * as a filename.
375 		 */
376 		if ((nbuf = malloc(strlen(detract(nsto, 0)) + 1)) == NULL)
377 			err(1, "Out of memory");
378 		if ((cp = yanklogin(detract(nsto, 0), nbuf)) != NULL)
379 			(void)savemail(expand(nbuf), mtf);
380 		free(nbuf);
381 		free(nsto);
382 	} else if ((cp = value("record")) != NULL)
383 		(void)savemail(expand(cp), mtf);
384 	/*
385 	 * Fork, set up the temporary mail file as standard
386 	 * input for "mail", and exec with the user list we generated
387 	 * far above.
388 	 */
389 	pid = fork();
390 	if (pid == -1) {
391 		warn("fork");
392 		savedeadletter(mtf);
393 		goto out;
394 	}
395 	if (pid == 0) {
396 		sigset_t nset;
397 		(void)sigemptyset(&nset);
398 		(void)sigaddset(&nset, SIGHUP);
399 		(void)sigaddset(&nset, SIGINT);
400 		(void)sigaddset(&nset, SIGQUIT);
401 		(void)sigaddset(&nset, SIGTSTP);
402 		(void)sigaddset(&nset, SIGTTIN);
403 		(void)sigaddset(&nset, SIGTTOU);
404 		prepare_child(&nset, fileno(mtf), -1);
405 		if ((cp = value("sendmail")) != NULL)
406 			cp = expand(cp);
407 		else
408 			cp = _PATH_SENDMAIL;
409 		execv(cp, namelist);
410 		warn("%s", cp);
411 		_exit(1);
412 	}
413 	if (value("verbose") != NULL)
414 		(void)wait_child(pid);
415 	else
416 		free_child(pid);
417 out:
418 	(void)Fclose(mtf);
419 }
420 
421 /*
422  * Fix the header by glopping all of the expanded names from
423  * the distribution list into the appropriate fields.
424  */
425 void
426 fixhead(struct header *hp, struct name *tolist)
427 {
428 	struct name *np;
429 
430 	hp->h_to = NULL;
431 	hp->h_cc = NULL;
432 	hp->h_bcc = NULL;
433 	for (np = tolist; np != NULL; np = np->n_flink) {
434 		/* Don't copy deleted addresses to the header */
435 		if (np->n_type & GDEL)
436 			continue;
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 /*
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,
475 	    GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA);
476 	c = getc(fi);
477 	while (c != EOF) {
478 		(void)putc(c, nfo);
479 		c = getc(fi);
480 	}
481 	if (ferror(fi)) {
482 		warnx("read");
483 		rewind(fi);
484 		return (fi);
485 	}
486 	(void)fflush(nfo);
487 	if (ferror(nfo)) {
488 		warn("%s", tempname);
489 		(void)Fclose(nfo);
490 		(void)Fclose(nfi);
491 		rewind(fi);
492 		return (fi);
493 	}
494 	(void)Fclose(nfo);
495 	(void)Fclose(fi);
496 	rewind(nfi);
497 	return (nfi);
498 }
499 
500 /*
501  * Dump the to, subject, cc header on the
502  * passed file buffer.
503  */
504 int
505 puthead(struct header *hp, FILE *fo, int w)
506 {
507 	int gotcha;
508 
509 	gotcha = 0;
510 	if (hp->h_to != NULL && w & GTO)
511 		fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
512 	if (hp->h_subject != NULL && w & GSUBJECT)
513 		fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
514 	if (hp->h_cc != NULL && w & GCC)
515 		fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
516 	if (hp->h_bcc != NULL && w & GBCC)
517 		fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
518 	if (hp->h_replyto != NULL && w & GREPLYTO)
519 		fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++;
520 	if (hp->h_inreplyto != NULL && w & GINREPLYTO)
521 		fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), gotcha++;
522 	if (gotcha && w & GNL)
523 		(void)putc('\n', fo);
524 	return (0);
525 }
526 
527 /*
528  * Format the given header line to not exceed 72 characters.
529  */
530 void
531 fmt(const char *str, struct name *np, FILE *fo, int comma)
532 {
533 	int col, len;
534 
535 	comma = comma ? 1 : 0;
536 	col = strlen(str);
537 	if (col)
538 		fputs(str, fo);
539 	for (; np != NULL; np = np->n_flink) {
540 		if (np->n_flink == NULL)
541 			comma = 0;
542 		len = strlen(np->n_name);
543 		col++;		/* for the space */
544 		if (col + len + comma > 72 && col > 4) {
545 			fprintf(fo, "\n    ");
546 			col = 4;
547 		} else
548 			fprintf(fo, " ");
549 		fputs(np->n_name, fo);
550 		if (comma)
551 			fprintf(fo, ",");
552 		col += len + comma;
553 	}
554 	fprintf(fo, "\n");
555 }
556 
557 /*
558  * Save the outgoing mail on the passed file.
559  */
560 
561 /*ARGSUSED*/
562 int
563 savemail(char name[], FILE *fi)
564 {
565 	FILE *fo;
566 	char buf[BUFSIZ];
567 	int i;
568 	time_t now;
569 
570 	if ((fo = Fopen(name, "a")) == NULL) {
571 		warn("%s", name);
572 		return (-1);
573 	}
574 	(void)time(&now);
575 	fprintf(fo, "From %s %s", myname, ctime(&now));
576 	while ((i = fread(buf, 1, sizeof(buf), fi)) > 0)
577 		(void)fwrite(buf, 1, i, fo);
578 	fprintf(fo, "\n");
579 	(void)fflush(fo);
580 	if (ferror(fo))
581 		warn("%s", name);
582 	(void)Fclose(fo);
583 	rewind(fi);
584 	return (0);
585 }
586