Initial import from FreeBSD RELENG_4:
[dragonfly.git] / usr.bin / mail / send.c
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
34 #ifndef lint
35 #if 0
36 static char sccsid[] = "@(#)send.c      8.1 (Berkeley) 6/6/93";
37 #endif
38 #endif /* not lint */
39 #include <sys/cdefs.h>
40 __FBSDID("$FreeBSD: src/usr.bin/mail/send.c,v 1.5.6.5 2003/01/06 05:46:03 mikeh Exp $");
41
42 #include "rcv.h"
43 #include "extern.h"
44
45 /*
46  * Mail -- a mail program
47  *
48  * Mail to others.
49  */
50
51 /*
52  * Send message described by the passed pointer to the
53  * passed output buffer.  Return -1 on error.
54  * Adjust the status: field if need be.
55  * If doign is given, suppress ignored header fields.
56  * prefix is a string to prepend to each output line.
57  */
58 int
59 sendmessage(mp, obuf, doign, prefix)
60         struct message *mp;
61         FILE *obuf;
62         struct ignoretab *doign;
63         char *prefix;
64 {
65         long count;
66         FILE *ibuf;
67         char *cp, *cp2, line[LINESIZE];
68         int ishead, infld, ignoring, dostat, firstline;
69         int c, length, prefixlen;
70
71         /*
72          * Compute the prefix string, without trailing whitespace
73          */
74         if (prefix != NULL) {
75                 cp2 = 0;
76                 for (cp = prefix; *cp != '\0'; cp++)
77                         if (*cp != ' ' && *cp != '\t')
78                                 cp2 = cp;
79                 prefixlen = cp2 == NULL ? 0 : cp2 - prefix + 1;
80         }
81         ibuf = setinput(mp);
82         count = mp->m_size;
83         ishead = 1;
84         dostat = doign == 0 || !isign("status", doign);
85         infld = 0;
86         firstline = 1;
87         /*
88          * Process headers first
89          */
90         while (count > 0 && ishead) {
91                 if (fgets(line, sizeof(line), ibuf) == NULL)
92                         break;
93                 count -= length = strlen(line);
94                 if (firstline) {
95                         /*
96                          * First line is the From line, so no headers
97                          * there to worry about
98                          */
99                         firstline = 0;
100                         ignoring = doign == ignoreall;
101                 } else if (line[0] == '\n') {
102                         /*
103                          * If line is blank, we've reached end of
104                          * headers, so force out status: field
105                          * and note that we are no longer in header
106                          * fields
107                          */
108                         if (dostat) {
109                                 statusput(mp, obuf, prefix);
110                                 dostat = 0;
111                         }
112                         ishead = 0;
113                         ignoring = doign == ignoreall;
114                 } else if (infld && (line[0] == ' ' || line[0] == '\t')) {
115                         /*
116                          * If this line is a continuation (via space or tab)
117                          * of a previous header field, just echo it
118                          * (unless the field should be ignored).
119                          * In other words, nothing to do.
120                          */
121                 } else {
122                         /*
123                          * Pick up the header field if we have one.
124                          */
125                         for (cp = line; (c = *cp++) != '\0' && c != ':' &&
126                             !isspace((unsigned char)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                                 ishead = 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                 count--;                /* skip final blank line */
194         if (prefix != NULL)
195                 while (count > 0) {
196                         if (fgets(line, sizeof(line), ibuf) == NULL) {
197                                 c = 0;
198                                 break;
199                         }
200                         count -= 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 (count > 0) {
216                         c = count < LINESIZE ? count : LINESIZE;
217                         if ((c = fread(line, sizeof(*line), c, ibuf)) <= 0)
218                                 break;
219                         count -= 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(mp, obuf, prefix)
235         struct message *mp;
236         FILE *obuf;
237         char *prefix;
238 {
239         char statout[3];
240         char *cp = statout;
241
242         if (mp->m_flag & MREAD)
243                 *cp++ = 'R';
244         if ((mp->m_flag & MNEW) == 0)
245                 *cp++ = 'O';
246         *cp = '\0';
247         if (statout[0] != '\0')
248                 fprintf(obuf, "%sStatus: %s\n",
249                         prefix == NULL ? "" : prefix, statout);
250 }
251
252 /*
253  * Interface between the argument list and the mail1 routine
254  * which does all the dirty work.
255  */
256 int
257 mail(to, cc, bcc, smopts, subject, replyto)
258         struct name *to, *cc, *bcc, *smopts;
259         char *subject, *replyto;
260 {
261         struct header head;
262
263         head.h_to = to;
264         head.h_subject = subject;
265         head.h_cc = cc;
266         head.h_bcc = bcc;
267         head.h_smopts = smopts;
268         head.h_replyto = replyto;
269         head.h_inreplyto = NULL;
270         mail1(&head, 0);
271         return (0);
272 }
273
274
275 /*
276  * Send mail to a bunch of user names.  The interface is through
277  * the mail routine below.
278  */
279 int
280 sendmail(str)
281         char *str;
282 {
283         struct header head;
284
285         head.h_to = extract(str, GTO);
286         head.h_subject = NULL;
287         head.h_cc = NULL;
288         head.h_bcc = NULL;
289         head.h_smopts = NULL;
290         head.h_replyto = value("REPLYTO");
291         head.h_inreplyto = NULL;
292         mail1(&head, 0);
293         return (0);
294 }
295
296 /*
297  * Mail a message on standard input to the people indicated
298  * in the passed header.  (Internal interface).
299  */
300 void
301 mail1(hp, printheaders)
302         struct header *hp;
303         int printheaders;
304 {
305         char *cp;
306         int pid;
307         char **namelist;
308         struct name *to;
309         FILE *mtf;
310
311         /*
312          * Collect user's mail from standard input.
313          * Get the result as mtf.
314          */
315         if ((mtf = collect(hp, printheaders)) == NULL)
316                 return;
317         if (value("interactive") != NULL) {
318                 if (value("askcc") != NULL || value("askbcc") != NULL) {
319                         if (value("askcc") != NULL)
320                                 grabh(hp, GCC);
321                         if (value("askbcc") != NULL)
322                                 grabh(hp, GBCC);
323                 } else {
324                         printf("EOT\n");
325                         (void)fflush(stdout);
326                 }
327         }
328         if (fsize(mtf) == 0) {
329                 if (value("dontsendempty") != NULL)
330                         goto out;
331                 if (hp->h_subject == NULL)
332                         printf("No message, no subject; hope that's ok\n");
333                 else
334                         printf("Null message body; hope that's ok\n");
335         }
336         /*
337          * Now, take the user names from the combined
338          * to and cc lists and do all the alias
339          * processing.
340          */
341         senderr = 0;
342         to = usermap(cat(hp->h_bcc, cat(hp->h_to, hp->h_cc)));
343         if (to == NULL) {
344                 printf("No recipients specified\n");
345                 senderr++;
346         }
347         /*
348          * Look through the recipient list for names with /'s
349          * in them which we write to as files directly.
350          */
351         to = outof(to, mtf, hp);
352         if (senderr)
353                 savedeadletter(mtf);
354         to = elide(to);
355         if (count(to) == 0)
356                 goto out;
357         fixhead(hp, to);
358         if ((mtf = infix(hp, mtf)) == NULL) {
359                 fprintf(stderr, ". . . message lost, sorry.\n");
360                 return;
361         }
362         namelist = unpack(cat(hp->h_smopts, to));
363         if (debug) {
364                 char **t;
365
366                 printf("Sendmail arguments:");
367                 for (t = namelist; *t != NULL; t++)
368                         printf(" \"%s\"", *t);
369                 printf("\n");
370                 goto out;
371         }
372         if ((cp = value("record")) != NULL)
373                 (void)savemail(expand(cp), mtf);
374         /*
375          * Fork, set up the temporary mail file as standard
376          * input for "mail", and exec with the user list we generated
377          * far above.
378          */
379         pid = fork();
380         if (pid == -1) {
381                 warn("fork");
382                 savedeadletter(mtf);
383                 goto out;
384         }
385         if (pid == 0) {
386                 sigset_t nset;
387                 (void)sigemptyset(&nset);
388                 (void)sigaddset(&nset, SIGHUP);
389                 (void)sigaddset(&nset, SIGINT);
390                 (void)sigaddset(&nset, SIGQUIT);
391                 (void)sigaddset(&nset, SIGTSTP);
392                 (void)sigaddset(&nset, SIGTTIN);
393                 (void)sigaddset(&nset, SIGTTOU);
394                 prepare_child(&nset, fileno(mtf), -1);
395                 if ((cp = value("sendmail")) != NULL)
396                         cp = expand(cp);
397                 else
398                         cp = _PATH_SENDMAIL;
399                 execv(cp, namelist);
400                 warn("%s", cp);
401                 _exit(1);
402         }
403         if (value("verbose") != NULL)
404                 (void)wait_child(pid);
405         else
406                 free_child(pid);
407 out:
408         (void)Fclose(mtf);
409 }
410
411 /*
412  * Fix the header by glopping all of the expanded names from
413  * the distribution list into the appropriate fields.
414  */
415 void
416 fixhead(hp, tolist)
417         struct header *hp;
418         struct name *tolist;
419 {
420         struct name *np;
421
422         hp->h_to = NULL;
423         hp->h_cc = NULL;
424         hp->h_bcc = NULL;
425         for (np = tolist; np != NULL; np = np->n_flink) {
426                 /* Don't copy deleted addresses to the header */
427                 if (np->n_type & GDEL)
428                         continue;
429                 if ((np->n_type & GMASK) == GTO)
430                         hp->h_to =
431                             cat(hp->h_to, nalloc(np->n_name, np->n_type));
432                 else if ((np->n_type & GMASK) == GCC)
433                         hp->h_cc =
434                             cat(hp->h_cc, nalloc(np->n_name, np->n_type));
435                 else if ((np->n_type & GMASK) == GBCC)
436                         hp->h_bcc =
437                             cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
438         }
439 }
440
441 /*
442  * Prepend a header in front of the collected stuff
443  * and return the new file.
444  */
445 FILE *
446 infix(hp, fi)
447         struct header *hp;
448         FILE *fi;
449 {
450         FILE *nfo, *nfi;
451         int c, fd;
452         char tempname[PATHSIZE];
453
454         (void)snprintf(tempname, sizeof(tempname),
455             "%s/mail.RsXXXXXXXXXX", tmpdir);
456         if ((fd = mkstemp(tempname)) == -1 ||
457             (nfo = Fdopen(fd, "w")) == NULL) {
458                 warn("%s", tempname);
459                 return (fi);
460         }
461         if ((nfi = Fopen(tempname, "r")) == NULL) {
462                 warn("%s", tempname);
463                 (void)Fclose(nfo);
464                 (void)rm(tempname);
465                 return (fi);
466         }
467         (void)rm(tempname);
468         (void)puthead(hp, nfo,
469             GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA);
470         c = getc(fi);
471         while (c != EOF) {
472                 (void)putc(c, nfo);
473                 c = getc(fi);
474         }
475         if (ferror(fi)) {
476                 warnx("read");
477                 rewind(fi);
478                 return (fi);
479         }
480         (void)fflush(nfo);
481         if (ferror(nfo)) {
482                 warn("%s", tempname);
483                 (void)Fclose(nfo);
484                 (void)Fclose(nfi);
485                 rewind(fi);
486                 return (fi);
487         }
488         (void)Fclose(nfo);
489         (void)Fclose(fi);
490         rewind(nfi);
491         return (nfi);
492 }
493
494 /*
495  * Dump the to, subject, cc header on the
496  * passed file buffer.
497  */
498 int
499 puthead(hp, fo, w)
500         struct header *hp;
501         FILE *fo;
502         int w;
503 {
504         int gotcha;
505
506         gotcha = 0;
507         if (hp->h_to != NULL && w & GTO)
508                 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
509         if (hp->h_subject != NULL && w & GSUBJECT)
510                 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
511         if (hp->h_cc != NULL && w & GCC)
512                 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
513         if (hp->h_bcc != NULL && w & GBCC)
514                 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
515         if (hp->h_replyto != NULL && w & GREPLYTO)
516                 fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++;
517         if (hp->h_inreplyto != NULL && w & GINREPLYTO)
518                 fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), gotcha++;
519         if (gotcha && w & GNL)
520                 (void)putc('\n', fo);
521         return (0);
522 }
523
524 /*
525  * Format the given header line to not exceed 72 characters.
526  */
527 void
528 fmt(str, np, fo, comma)
529         const char *str;
530         struct name *np;
531         FILE *fo;
532         int comma;
533 {
534         int col, len;
535
536         comma = comma ? 1 : 0;
537         col = strlen(str);
538         if (col)
539                 fputs(str, fo);
540         for (; np != NULL; np = np->n_flink) {
541                 if (np->n_flink == NULL)
542                         comma = 0;
543                 len = strlen(np->n_name);
544                 col++;          /* for the space */
545                 if (col + len + comma > 72 && col > 4) {
546                         fprintf(fo, "\n    ");
547                         col = 4;
548                 } else
549                         fprintf(fo, " ");
550                 fputs(np->n_name, fo);
551                 if (comma)
552                         fprintf(fo, ",");
553                 col += len + comma;
554         }
555         fprintf(fo, "\n");
556 }
557
558 /*
559  * Save the outgoing mail on the passed file.
560  */
561
562 /*ARGSUSED*/
563 int
564 savemail(name, fi)
565         char name[];
566         FILE *fi;
567 {
568         FILE *fo;
569         char buf[BUFSIZ];
570         int i;
571         time_t now;
572
573         if ((fo = Fopen(name, "a")) == NULL) {
574                 warn("%s", name);
575                 return (-1);
576         }
577         (void)time(&now);
578         fprintf(fo, "From %s %s", myname, ctime(&now));
579         while ((i = fread(buf, 1, sizeof(buf), fi)) > 0)
580                 (void)fwrite(buf, 1, i, fo);
581         fprintf(fo, "\n");
582         (void)fflush(fo);
583         if (ferror(fo))
584                 warn("%s", name);
585         (void)Fclose(fo);
586         rewind(fi);
587         return (0);
588 }