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