Home | History | Annotate | Line # | Download | only in mail
send.c revision 1.26
      1 /*	$NetBSD: send.c,v 1.26 2006/10/21 21:37:21 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 #if 0
     35 static char sccsid[] = "@(#)send.c	8.1 (Berkeley) 6/6/93";
     36 #else
     37 __RCSID("$NetBSD: send.c,v 1.26 2006/10/21 21:37:21 christos Exp $");
     38 #endif
     39 #endif /* not lint */
     40 
     41 #include "rcv.h"
     42 #include "extern.h"
     43 #ifdef MIME_SUPPORT
     44 #include "mime.h"
     45 #endif
     46 
     47 
     48 /*
     49  * Mail -- a mail program
     50  *
     51  * Mail to others.
     52  */
     53 
     54 /*
     55  * Send message described by the passed pointer to the
     56  * passed output buffer.  Return -1 on error.
     57  * Adjust the status: field if need be.
     58  * If doign is given, suppress ignored header fields.
     59  * prefix is a string to prepend to each output line.
     60  */
     61 int
     62 #ifdef MIME_SUPPORT
     63 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
     64     const char *prefix, struct mime_info *mip)
     65 #else
     66 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
     67     const char *prefix)
     68 #endif /* MIME_SUPPORT */
     69 {
     70 	off_t len;
     71 	FILE *ibuf;
     72 	char line[LINESIZE];
     73 	int isheadflag, infld, ignoring = 0, dostat, firstline;
     74 	char *cp, *cp2;
     75 	int c = 0;
     76 	size_t length;
     77 	size_t prefixlen = 0;
     78 
     79 	/*
     80 	 * Compute the prefix string, without trailing whitespace
     81 	 */
     82 	if (prefix != NULL) {
     83 		const char *dp, *dp2 = NULL;
     84 		for (dp = prefix; *dp; dp++)
     85 			if (*dp != ' ' && *dp != '\t')
     86 				dp2 = dp;
     87 		prefixlen = dp2 == 0 ? 0 : dp2 - prefix + 1;
     88 	}
     89 	ibuf = setinput(mp);
     90 	len = mp->m_size;
     91 	isheadflag = 1;
     92 	dostat = doign == 0 || !isign("status", doign);
     93 	infld = 0;
     94 	firstline = 1;
     95 	/*
     96 	 * Process headers first
     97 	 */
     98 #ifdef MIME_SUPPORT
     99 	if (mip)
    100 		obuf = mime_decode_header(mip);
    101 #endif
    102 	while (len > 0 && isheadflag) {
    103 		if (fgets(line, LINESIZE, ibuf) == NULL)
    104 			break;
    105 		len -= length = strlen(line);
    106 		if (firstline) {
    107 			/*
    108 			 * First line is the From line, so no headers
    109 			 * there to worry about
    110 			 */
    111 			firstline = 0;
    112 			ignoring = doign == ignoreall;
    113 #ifdef MIME_SUPPORT
    114 			/* XXX - ignore multipart boundary lines! */
    115 			if (line[0] == '-' && line[1] == '-')
    116 				ignoring = 1;
    117 #endif
    118 		} else if (line[0] == '\n') {
    119 			/*
    120 			 * If line is blank, we've reached end of
    121 			 * headers, so force out status: field
    122 			 * and note that we are no longer in header
    123 			 * fields
    124 			 */
    125 			if (dostat) {
    126 				statusput(mp, obuf, prefix);
    127 				dostat = 0;
    128 			}
    129 			isheadflag = 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((unsigned char)*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 					statusput(mp, obuf, prefix);
    155 					dostat = 0;
    156 				}
    157 				if (doign != ignoreall)
    158 					/* add blank line */
    159 					(void)putc('\n', obuf);
    160 				isheadflag = 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 						statusput(mp, obuf, prefix);
    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 					(void)fputs(prefix, obuf);
    196 				else
    197 					(void)fwrite(prefix, sizeof *prefix,
    198 							prefixlen, obuf);
    199 			}
    200 			(void)fwrite(line, sizeof *line, length, obuf);
    201 			if (ferror(obuf))
    202 				return -1;
    203 		}
    204 	}
    205 	/*
    206 	 * Copy out message body
    207 	 */
    208 #ifdef MIME_SUPPORT
    209 	if (mip) {
    210 		obuf = mime_decode_body(mip);
    211 		if (obuf == NULL) /* XXX - early out */
    212 			return 0;
    213 	}
    214 #endif
    215 	if (doign == ignoreall)
    216 		len--;		/* skip final blank line */
    217 	if (prefix != NULL)
    218 		while (len > 0) {
    219 			if (fgets(line, LINESIZE, ibuf) == NULL) {
    220 				c = 0;
    221 				break;
    222 			}
    223 			len -= c = strlen(line);
    224 			/*
    225 			 * Strip trailing whitespace from prefix
    226 			 * if line is blank.
    227 			 */
    228 			if (c > 1)
    229 				(void)fputs(prefix, obuf);
    230 			else
    231 				(void)fwrite(prefix, sizeof *prefix,
    232 						prefixlen, obuf);
    233 			(void)fwrite(line, sizeof *line, (size_t)c, obuf);
    234 			if (ferror(obuf))
    235 				return -1;
    236 		}
    237 	else
    238 		while (len > 0) {
    239 			c = (int)(len < LINESIZE ? len : LINESIZE);
    240 			if ((c = fread(line, sizeof *line, (size_t)c, ibuf)) <= 0)
    241 				break;
    242 			len -= c;
    243 			if (fwrite(line, sizeof *line, (size_t)c, obuf) != c)
    244 				return -1;
    245 		}
    246 	if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
    247 		/* no final blank line */
    248 		if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
    249 				return -1;
    250 	return 0;
    251 }
    252 
    253 /*
    254  * Output a reasonable looking status field.
    255  */
    256 void
    257 statusput(struct message *mp, FILE *obuf, const char *prefix)
    258 {
    259 	char statout[3];
    260 	char *cp = statout;
    261 
    262 	if (mp->m_flag & MREAD)
    263 		*cp++ = 'R';
    264 	if ((mp->m_flag & MNEW) == 0)
    265 		*cp++ = 'O';
    266 	*cp = 0;
    267 	if (statout[0])
    268 		(void)fprintf(obuf, "%sStatus: %s\n",
    269 			prefix == NULL ? "" : prefix, statout);
    270 }
    271 
    272 /*
    273  * Interface between the argument list and the mail1 routine
    274  * which does all the dirty work.
    275  */
    276 #ifdef MIME_SUPPORT
    277 int
    278 mail(struct name *to, struct name *cc, struct name *bcc,
    279      struct name *smopts, char *subject, struct attachment *attach)
    280 #else
    281 int
    282 mail(struct name *to, struct name *cc, struct name *bcc,
    283      struct name *smopts, char *subject)
    284 #endif
    285 {
    286 	struct header head;
    287 
    288 	head.h_to = to;
    289 	head.h_subject = subject;
    290 	head.h_cc = cc;
    291 	head.h_bcc = bcc;
    292 	head.h_smopts = smopts;
    293 #ifdef MIME_SUPPORT
    294 	head.h_attach = attach;
    295 #endif
    296 	mail1(&head, 0);
    297 	return(0);
    298 }
    299 
    300 
    301 /*
    302  * Send mail to a bunch of user names.  The interface is through
    303  * the mail routine below.
    304  */
    305 int
    306 sendmail(void *v)
    307 {
    308 	char *str = v;
    309 	struct header head;
    310 
    311 	head.h_to = extract(str, GTO);
    312 	head.h_subject = NULL;
    313 	head.h_cc = NULL;
    314 	head.h_bcc = NULL;
    315 	head.h_smopts = NULL;
    316 #ifdef MIME_SUPPORT
    317 	head.h_attach = NULL;
    318 #endif
    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 	const char *cp;
    331 	int pid;
    332 	const 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 (value("interactive") != NULL) {
    343 		if (value("askcc") != NULL || value("askbcc") != NULL) {
    344 			if (value("askcc") != NULL)
    345 				(void)grabh(hp, GCC);
    346 			if (value("askbcc") != NULL)
    347 				(void)grabh(hp, GBCC);
    348 		} else {
    349 			(void)printf("EOT\n");
    350 			(void)fflush(stdout);
    351 		}
    352 	}
    353 	if (fsize(mtf) == 0) {
    354 		if (value("dontsendempty") != NULL)
    355 			goto out;
    356 		if (hp->h_subject == NULL)
    357 			(void)printf("No message, no subject; hope that's ok\n");
    358 		else
    359 			(void)printf("Null message body; hope that's ok\n");
    360 	}
    361 	/*
    362 	 * Now, take the user names from the combined
    363 	 * to and cc lists and do all the alias
    364 	 * processing.
    365 	 */
    366 	senderr = 0;
    367 	to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
    368 	if (to == NULL) {
    369 		(void)printf("No recipients specified\n");
    370 		senderr++;
    371 	}
    372 #ifdef MIME_SUPPORT
    373 	/*
    374 	 * If there are attachments, repackage the mail as a
    375 	 * multi-part MIME message.
    376 	 */
    377 	if (hp->h_attach || value(ENAME_MIME_ENCODE_MSG))
    378 		mtf = mime_encode(mtf, hp);
    379 #endif
    380 	/*
    381 	 * Look through the recipient list for names with /'s
    382 	 * in them which we write to as files directly.
    383 	 */
    384 	to = outof(to, mtf, hp);
    385 	if (senderr)
    386 		savedeadletter(mtf);
    387 	to = elide(to);
    388 	if (count(to) == 0)
    389 		goto out;
    390 	fixhead(hp, to);
    391 	if ((mtf = infix(hp, mtf)) == NULL) {
    392 		(void)fprintf(stderr, ". . . message lost, sorry.\n");
    393 		return;
    394 	}
    395 	namelist = unpack(cat(hp->h_smopts, to));
    396 	if (debug) {
    397 		const char **t;
    398 
    399 		(void)printf("Sendmail arguments:");
    400 		for (t = namelist; *t != NULL; t++)
    401 			(void)printf(" \"%s\"", *t);
    402 		(void)printf("\n");
    403 		goto out;
    404 	}
    405 	if ((cp = value("record")) != NULL)
    406 		(void)savemail(expand(cp), mtf, hp->h_to);
    407 	/*
    408 	 * Fork, set up the temporary mail file as standard
    409 	 * input for "mail", and exec with the user list we generated
    410 	 * far above.
    411 	 */
    412 	pid = fork();
    413 	if (pid == -1) {
    414 		warn("fork");
    415 		savedeadletter(mtf);
    416 		goto out;
    417 	}
    418 	if (pid == 0) {
    419 		sigset_t nset;
    420 		(void)sigemptyset(&nset);
    421 		(void)sigaddset(&nset, SIGHUP);
    422 		(void)sigaddset(&nset, SIGINT);
    423 		(void)sigaddset(&nset, SIGQUIT);
    424 		(void)sigaddset(&nset, SIGTSTP);
    425 		(void)sigaddset(&nset, SIGTTIN);
    426 		(void)sigaddset(&nset, SIGTTOU);
    427 		prepare_child(&nset, fileno(mtf), -1);
    428 		if ((cp = value("sendmail")) != NULL)
    429 			cp = expand(cp);
    430 		else
    431 			cp = _PATH_SENDMAIL;
    432 		(void)execv(cp, (char *const *)__UNCONST(namelist));
    433 		warn("%s", cp);
    434 		_exit(1);
    435 	}
    436 	if (value("verbose") != NULL)
    437 		(void)wait_child(pid);
    438 	else
    439 		free_child(pid);
    440 out:
    441 	(void)Fclose(mtf);
    442 }
    443 
    444 /*
    445  * Fix the header by glopping all of the expanded names from
    446  * the distribution list into the appropriate fields.
    447  */
    448 void
    449 fixhead(struct header *hp, struct name *tolist)
    450 {
    451 	struct name *np;
    452 
    453 	hp->h_to = NULL;
    454 	hp->h_cc = NULL;
    455 	hp->h_bcc = NULL;
    456 	for (np = tolist; np != NULL; np = np->n_flink) {
    457 		if (np->n_type & GDEL)
    458 			continue;	/* Don't copy deleted addresses to the header */
    459 		if ((np->n_type & GMASK) == GTO)
    460 			hp->h_to =
    461 				cat(hp->h_to, nalloc(np->n_name, np->n_type));
    462 		else if ((np->n_type & GMASK) == GCC)
    463 			hp->h_cc =
    464 				cat(hp->h_cc, nalloc(np->n_name, np->n_type));
    465 		else if ((np->n_type & GMASK) == GBCC)
    466 			hp->h_bcc =
    467 				cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
    468 	}
    469 }
    470 
    471 /*
    472  * Prepend a header in front of the collected stuff
    473  * and return the new file.
    474  */
    475 FILE *
    476 infix(struct header *hp, FILE *fi)
    477 {
    478 	FILE *nfo, *nfi;
    479 	int c, fd;
    480 	char tempname[PATHSIZE];
    481 
    482 	(void)snprintf(tempname, sizeof(tempname),
    483 	    "%s/mail.RsXXXXXXXXXX", tmpdir);
    484 	if ((fd = mkstemp(tempname)) == -1 ||
    485 	    (nfo = Fdopen(fd, "w")) == NULL) {
    486 		if (fd != -1)
    487 			(void)close(fd);
    488 		warn("%s", tempname);
    489 		return(fi);
    490 	}
    491 	if ((nfi = Fopen(tempname, "r")) == NULL) {
    492 		warn("%s", tempname);
    493 		(void)Fclose(nfo);
    494 		(void)rm(tempname);
    495 		return(fi);
    496 	}
    497 	(void)rm(tempname);
    498 #ifdef MIME_SUPPORT
    499 	(void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GMIME|GNL|GCOMMA);
    500 #else
    501 	(void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
    502 #endif
    503 
    504 	c = getc(fi);
    505 	while (c != EOF) {
    506 		(void)putc(c, nfo);
    507 		c = getc(fi);
    508 	}
    509 	if (ferror(fi)) {
    510 		warn("read");
    511 		rewind(fi);
    512 		return(fi);
    513 	}
    514 	(void)fflush(nfo);
    515 	if (ferror(nfo)) {
    516 		warn("%s", tempname);
    517 		(void)Fclose(nfo);
    518 		(void)Fclose(nfi);
    519 		rewind(fi);
    520 		return(fi);
    521 	}
    522 	(void)Fclose(nfo);
    523 	(void)Fclose(fi);
    524 	rewind(nfi);
    525 	return(nfi);
    526 }
    527 
    528 /*
    529  * Dump the to, subject, cc header on the
    530  * passed file buffer.
    531  */
    532 int
    533 puthead(struct header *hp, FILE *fo, int w)
    534 {
    535 	int gotcha;
    536 
    537 	gotcha = 0;
    538 	if (hp->h_to != NULL && w & GTO)
    539 		if (!Bflag)
    540 			fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
    541 	if (hp->h_subject != NULL && w & GSUBJECT)
    542 		(void)fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
    543 	if (hp->h_smopts != NULL && w & GSMOPTS)
    544 		(void)fprintf(fo, "(sendmail options: %s)\n", detract(hp->h_smopts, GSMOPTS)), gotcha++;
    545 	if (hp->h_cc != NULL && w & GCC)
    546 		fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
    547 	if (hp->h_bcc != NULL && w & GBCC)
    548 		fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
    549 #ifdef MIME_SUPPORT
    550 	if (w & GMIME && (hp->h_attach || value(ENAME_MIME_ENCODE_MSG)))
    551 		mime_putheader(fo, hp), gotcha++;
    552 #endif
    553 	if (gotcha && w & GNL)
    554 		(void)putc('\n', fo);
    555 	return(0);
    556 }
    557 
    558 /*
    559  * Format the given header line to not exceed 72 characters.
    560  */
    561 void
    562 fmt(const char *str, struct name *np, FILE *fo, int comma)
    563 {
    564 	int col, len;
    565 
    566 	comma = comma ? 1 : 0;
    567 	col = strlen(str);
    568 	if (col)
    569 		(void)fputs(str, fo);
    570 	for (; np != NULL; np = np->n_flink) {
    571 		if (np->n_flink == NULL)
    572 			comma = 0;
    573 		len = strlen(np->n_name);
    574 		col++;		/* for the space */
    575 		if (col + len + comma > 72 && col > 4) {
    576 			(void)fputs("\n    ", fo);
    577 			col = 4;
    578 		} else
    579 			(void)putc(' ', fo);
    580 		(void)fputs(np->n_name, fo);
    581 		if (comma)
    582 			(void)putc(',', fo);
    583 		col += len + comma;
    584 	}
    585 	(void)putc('\n', fo);
    586 }
    587 
    588 /*
    589  * Save the outgoing mail on the passed file.
    590  */
    591 
    592 /*ARGSUSED*/
    593 int
    594 savemail(const char name[], FILE *fi, struct name *to)
    595 {
    596 	FILE *fo;
    597 	char buf[BUFSIZ];
    598 	int i;
    599 	time_t now;
    600 	mode_t m;
    601 
    602 	m = umask(077);
    603 	fo = Fopen(name, "a");
    604 	(void)umask(m);
    605 	if (fo == NULL) {
    606 		warn("%s", name);
    607 		return (-1);
    608 	}
    609 	(void)time(&now);
    610 	(void)fprintf(fo, "From %s %s", myname, ctime(&now));
    611 
    612 	if (Bflag) { /* Make sure we save a "To:" line if -B is set */
    613 		struct name *n;
    614 		(void)fputs("To: undisclosed-recipients:", fo);
    615 		for (n = to; n != NULL; n = n->n_flink)
    616 			if ((n->n_type & GDEL) == 0)
    617 				(void)fprintf(fo, " %s", n->n_name);
    618 		(void)putc('\n', fo);
    619 	}
    620 
    621 	while ((i = fread(buf, 1, sizeof buf, fi)) > 0)
    622 		(void)fwrite(buf, 1, (size_t)i, fo);
    623 	(void)putc('\n', fo);
    624 	(void)fflush(fo);
    625 	if (ferror(fo))
    626 		warn("%s", name);
    627 	(void)Fclose(fo);
    628 	rewind(fi);
    629 	return (0);
    630 }
    631