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