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