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