send.c revision 1.21 1 /* $NetBSD: send.c,v 1.21 2003/08/07 11:14:41 agc 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.21 2003/08/07 11:14:41 agc Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include "rcv.h"
42 #include "extern.h"
43
44 /*
45 * Mail -- a mail program
46 *
47 * Mail to others.
48 */
49
50 extern char *tmpdir;
51
52 /*
53 * Send message described by the passed pointer to the
54 * passed output buffer. Return -1 on error.
55 * Adjust the status: field if need be.
56 * If doign is given, suppress ignored header fields.
57 * prefix is a string to prepend to each output line.
58 */
59 int
60 sendmessage(struct message *mp, FILE *obuf, struct ignoretab *doign,
61 char *prefix)
62 {
63 long len;
64 FILE *ibuf;
65 char line[LINESIZE];
66 int isheadflag, infld, ignoring = 0, dostat, firstline;
67 char *cp, *cp2;
68 int c = 0;
69 int length;
70 int prefixlen = 0;
71
72 /*
73 * Compute the prefix string, without trailing whitespace
74 */
75 if (prefix != NULL) {
76 cp2 = 0;
77 for (cp = prefix; *cp; cp++)
78 if (*cp != ' ' && *cp != '\t')
79 cp2 = cp;
80 prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
81 }
82 ibuf = setinput(mp);
83 len = mp->m_size;
84 isheadflag = 1;
85 dostat = doign == 0 || !isign("status", doign);
86 infld = 0;
87 firstline = 1;
88 /*
89 * Process headers first
90 */
91 while (len > 0 && isheadflag) {
92 if (fgets(line, LINESIZE, ibuf) == NULL)
93 break;
94 len -= length = strlen(line);
95 if (firstline) {
96 /*
97 * First line is the From line, so no headers
98 * there to worry about
99 */
100 firstline = 0;
101 ignoring = doign == ignoreall;
102 } else if (line[0] == '\n') {
103 /*
104 * If line is blank, we've reached end of
105 * headers, so force out status: field
106 * and note that we are no longer in header
107 * fields
108 */
109 if (dostat) {
110 statusput(mp, obuf, prefix);
111 dostat = 0;
112 }
113 isheadflag = 0;
114 ignoring = doign == ignoreall;
115 } else if (infld && (line[0] == ' ' || line[0] == '\t')) {
116 /*
117 * If this line is a continuation (via space or tab)
118 * of a previous header field, just echo it
119 * (unless the field should be ignored).
120 * In other words, nothing to do.
121 */
122 } else {
123 /*
124 * Pick up the header field if we have one.
125 */
126 for (cp = line; (c = *cp++) && c != ':' && !isspace(c);)
127 ;
128 cp2 = --cp;
129 while (isspace((unsigned char)*cp++))
130 ;
131 if (cp[-1] != ':') {
132 /*
133 * Not a header line, force out status:
134 * This happens in uucp style mail where
135 * there are no headers at all.
136 */
137 if (dostat) {
138 statusput(mp, obuf, prefix);
139 dostat = 0;
140 }
141 if (doign != ignoreall)
142 /* add blank line */
143 (void)putc('\n', obuf);
144 isheadflag = 0;
145 ignoring = 0;
146 } else {
147 /*
148 * If it is an ignored field and
149 * we care about such things, skip it.
150 */
151 *cp2 = 0; /* temporarily null terminate */
152 if (doign && isign(line, doign))
153 ignoring = 1;
154 else if ((line[0] == 's' || line[0] == 'S') &&
155 strcasecmp(line, "status") == 0) {
156 /*
157 * If the field is "status," go compute
158 * and print the real Status: field
159 */
160 if (dostat) {
161 statusput(mp, obuf, prefix);
162 dostat = 0;
163 }
164 ignoring = 1;
165 } else {
166 ignoring = 0;
167 *cp2 = c; /* restore */
168 }
169 infld = 1;
170 }
171 }
172 if (!ignoring) {
173 /*
174 * Strip trailing whitespace from prefix
175 * if line is blank.
176 */
177 if (prefix != NULL) {
178 if (length > 1)
179 fputs(prefix, obuf);
180 else
181 (void)fwrite(prefix, sizeof *prefix,
182 prefixlen, obuf);
183 }
184 (void)fwrite(line, sizeof *line, length, obuf);
185 if (ferror(obuf))
186 return -1;
187 }
188 }
189 /*
190 * Copy out message body
191 */
192 if (doign == ignoreall)
193 len--; /* skip final blank line */
194 if (prefix != NULL)
195 while (len > 0) {
196 if (fgets(line, LINESIZE, ibuf) == NULL) {
197 c = 0;
198 break;
199 }
200 len -= c = strlen(line);
201 /*
202 * Strip trailing whitespace from prefix
203 * if line is blank.
204 */
205 if (c > 1)
206 fputs(prefix, obuf);
207 else
208 (void)fwrite(prefix, sizeof *prefix,
209 prefixlen, obuf);
210 (void)fwrite(line, sizeof *line, c, obuf);
211 if (ferror(obuf))
212 return -1;
213 }
214 else
215 while (len > 0) {
216 c = len < LINESIZE ? len : LINESIZE;
217 if ((c = fread(line, sizeof *line, c, ibuf)) <= 0)
218 break;
219 len -= c;
220 if (fwrite(line, sizeof *line, c, obuf) != c)
221 return -1;
222 }
223 if (doign == ignoreall && c > 0 && line[c - 1] != '\n')
224 /* no final blank line */
225 if ((c = getc(ibuf)) != EOF && putc(c, obuf) == EOF)
226 return -1;
227 return 0;
228 }
229
230 /*
231 * Output a reasonable looking status field.
232 */
233 void
234 statusput(struct message *mp, FILE *obuf, char *prefix)
235 {
236 char statout[3];
237 char *cp = statout;
238
239 if (mp->m_flag & MREAD)
240 *cp++ = 'R';
241 if ((mp->m_flag & MNEW) == 0)
242 *cp++ = 'O';
243 *cp = 0;
244 if (statout[0])
245 fprintf(obuf, "%sStatus: %s\n",
246 prefix == NULL ? "" : prefix, statout);
247 }
248
249 /*
250 * Interface between the argument list and the mail1 routine
251 * which does all the dirty work.
252 */
253 int
254 mail(struct name *to, struct name *cc, struct name *bcc,
255 struct name *smopts, char *subject)
256 {
257 struct header head;
258
259 head.h_to = to;
260 head.h_subject = subject;
261 head.h_cc = cc;
262 head.h_bcc = bcc;
263 head.h_smopts = smopts;
264 mail1(&head, 0);
265 return(0);
266 }
267
268
269 /*
270 * Send mail to a bunch of user names. The interface is through
271 * the mail routine below.
272 */
273 int
274 sendmail(void *v)
275 {
276 char *str = v;
277 struct header head;
278
279 head.h_to = extract(str, GTO);
280 head.h_subject = NULL;
281 head.h_cc = NULL;
282 head.h_bcc = NULL;
283 head.h_smopts = NULL;
284 mail1(&head, 0);
285 return(0);
286 }
287
288 /*
289 * Mail a message on standard input to the people indicated
290 * in the passed header. (Internal interface).
291 */
292 void
293 mail1(struct header *hp, int printheaders)
294 {
295 char *cp;
296 int pid;
297 char **namelist;
298 struct name *to;
299 FILE *mtf;
300
301 /*
302 * Collect user's mail from standard input.
303 * Get the result as mtf.
304 */
305 if ((mtf = collect(hp, printheaders)) == NULL)
306 return;
307 if (value("interactive") != NULL) {
308 if (value("askcc") != NULL || value("askbcc") != NULL) {
309 if (value("askcc") != NULL)
310 grabh(hp, GCC);
311 if (value("askbcc") != NULL)
312 grabh(hp, GBCC);
313 } else {
314 printf("EOT\n");
315 (void)fflush(stdout);
316 }
317 }
318 if (fsize(mtf) == 0) {
319 if (value("dontsendempty") != NULL)
320 goto out;
321 if (hp->h_subject == NULL)
322 printf("No message, no subject; hope that's ok\n");
323 else
324 printf("Null message body; hope that's ok\n");
325 }
326 /*
327 * Now, take the user names from the combined
328 * to and cc lists and do all the alias
329 * processing.
330 */
331 senderr = 0;
332 to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
333 if (to == NULL) {
334 printf("No recipients specified\n");
335 senderr++;
336 }
337 /*
338 * Look through the recipient list for names with /'s
339 * in them which we write to as files directly.
340 */
341 to = outof(to, mtf, hp);
342 if (senderr)
343 savedeadletter(mtf);
344 to = elide(to);
345 if (count(to) == 0)
346 goto out;
347 fixhead(hp, to);
348 if ((mtf = infix(hp, mtf)) == NULL) {
349 fprintf(stderr, ". . . message lost, sorry.\n");
350 return;
351 }
352 namelist = unpack(cat(hp->h_smopts, to));
353 if (debug) {
354 char **t;
355
356 printf("Sendmail arguments:");
357 for (t = namelist; *t != NULL; t++)
358 printf(" \"%s\"", *t);
359 printf("\n");
360 goto out;
361 }
362 if ((cp = value("record")) != NULL)
363 (void)savemail(expand(cp), mtf);
364 /*
365 * Fork, set up the temporary mail file as standard
366 * input for "mail", and exec with the user list we generated
367 * far above.
368 */
369 pid = fork();
370 if (pid == -1) {
371 warn("fork");
372 savedeadletter(mtf);
373 goto out;
374 }
375 if (pid == 0) {
376 sigset_t nset;
377 sigemptyset(&nset);
378 sigaddset(&nset, SIGHUP);
379 sigaddset(&nset, SIGINT);
380 sigaddset(&nset, SIGQUIT);
381 sigaddset(&nset, SIGTSTP);
382 sigaddset(&nset, SIGTTIN);
383 sigaddset(&nset, SIGTTOU);
384 prepare_child(&nset, fileno(mtf), -1);
385 if ((cp = value("sendmail")) != NULL)
386 cp = expand(cp);
387 else
388 cp = _PATH_SENDMAIL;
389 execv(cp, namelist);
390 warn("%s", cp);
391 _exit(1);
392 }
393 if (value("verbose") != NULL)
394 (void)wait_child(pid);
395 else
396 free_child(pid);
397 out:
398 (void)Fclose(mtf);
399 }
400
401 /*
402 * Fix the header by glopping all of the expanded names from
403 * the distribution list into the appropriate fields.
404 */
405 void
406 fixhead(struct header *hp, struct name *tolist)
407 {
408 struct name *np;
409
410 hp->h_to = NULL;
411 hp->h_cc = NULL;
412 hp->h_bcc = NULL;
413 for (np = tolist; np != NULL; np = np->n_flink) {
414 if (np->n_type & GDEL)
415 continue; /* Don't copy deleted addresses to the header */
416 if ((np->n_type & GMASK) == GTO)
417 hp->h_to =
418 cat(hp->h_to, nalloc(np->n_name, np->n_type));
419 else if ((np->n_type & GMASK) == GCC)
420 hp->h_cc =
421 cat(hp->h_cc, nalloc(np->n_name, np->n_type));
422 else if ((np->n_type & GMASK) == GBCC)
423 hp->h_bcc =
424 cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
425 }
426 }
427
428 /*
429 * Prepend a header in front of the collected stuff
430 * and return the new file.
431 */
432 FILE *
433 infix(struct header *hp, FILE *fi)
434 {
435 FILE *nfo, *nfi;
436 int c, fd;
437 char tempname[PATHSIZE];
438
439 (void)snprintf(tempname, sizeof(tempname),
440 "%s/mail.RsXXXXXXXXXX", tmpdir);
441 if ((fd = mkstemp(tempname)) == -1 ||
442 (nfo = Fdopen(fd, "w")) == NULL) {
443 if (fd != -1)
444 close(fd);
445 warn("%s", tempname);
446 return(fi);
447 }
448 if ((nfi = Fopen(tempname, "r")) == NULL) {
449 warn("%s", tempname);
450 (void)Fclose(nfo);
451 (void)rm(tempname);
452 return(fi);
453 }
454 (void)rm(tempname);
455 (void)puthead(hp, nfo, GTO|GSUBJECT|GCC|GBCC|GNL|GCOMMA);
456 c = getc(fi);
457 while (c != EOF) {
458 (void)putc(c, nfo);
459 c = getc(fi);
460 }
461 if (ferror(fi)) {
462 warn("read");
463 rewind(fi);
464 return(fi);
465 }
466 (void)fflush(nfo);
467 if (ferror(nfo)) {
468 warn("%s", tempname);
469 (void)Fclose(nfo);
470 (void)Fclose(nfi);
471 rewind(fi);
472 return(fi);
473 }
474 (void)Fclose(nfo);
475 (void)Fclose(fi);
476 rewind(nfi);
477 return(nfi);
478 }
479
480 /*
481 * Dump the to, subject, cc header on the
482 * passed file buffer.
483 */
484 int
485 puthead(struct header *hp, FILE *fo, int w)
486 {
487 int gotcha;
488
489 gotcha = 0;
490 if (hp->h_to != NULL && w & GTO)
491 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
492 if (hp->h_subject != NULL && w & GSUBJECT)
493 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
494 if (hp->h_cc != NULL && w & GCC)
495 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
496 if (hp->h_bcc != NULL && w & GBCC)
497 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), 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(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 fputs("\n ", fo);
522 col = 4;
523 } else
524 putc(' ', fo);
525 fputs(np->n_name, fo);
526 if (comma)
527 putc(',', fo);
528 col += len + comma;
529 }
530 putc('\n', fo);
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 (void)putc('\n', fo);
555 (void)fflush(fo);
556 if (ferror(fo))
557 warn("%s", name);
558 (void)Fclose(fo);
559 rewind(fi);
560 return (0);
561 }
562