Double semi-colon police!
[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.3 2003/10/04 20:36:48 hmp 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                                         (void)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                                         (void)fwrite(prefix, sizeof(*prefix),
175                                             prefixlen, obuf);
176                         }
177                         (void)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                                 (void)fwrite(prefix, sizeof(*prefix),
202                                     prefixlen, obuf);
203                         (void)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         int pid;
293         char **namelist;
294         struct name *to;
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                         (void)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         fixhead(hp, to);
344         if ((mtf = infix(hp, mtf)) == NULL) {
345                 fprintf(stderr, ". . . message lost, sorry.\n");
346                 return;
347         }
348         namelist = unpack(cat(hp->h_smopts, to));
349         if (debug) {
350                 char **t;
351
352                 printf("Sendmail arguments:");
353                 for (t = namelist; *t != NULL; t++)
354                         printf(" \"%s\"", *t);
355                 printf("\n");
356                 goto out;
357         }
358         if ((cp = value("record")) != NULL)
359                 (void)savemail(expand(cp), mtf);
360         /*
361          * Fork, set up the temporary mail file as standard
362          * input for "mail", and exec with the user list we generated
363          * far above.
364          */
365         pid = fork();
366         if (pid == -1) {
367                 warn("fork");
368                 savedeadletter(mtf);
369                 goto out;
370         }
371         if (pid == 0) {
372                 sigset_t nset;
373                 (void)sigemptyset(&nset);
374                 (void)sigaddset(&nset, SIGHUP);
375                 (void)sigaddset(&nset, SIGINT);
376                 (void)sigaddset(&nset, SIGQUIT);
377                 (void)sigaddset(&nset, SIGTSTP);
378                 (void)sigaddset(&nset, SIGTTIN);
379                 (void)sigaddset(&nset, SIGTTOU);
380                 prepare_child(&nset, fileno(mtf), -1);
381                 if ((cp = value("sendmail")) != NULL)
382                         cp = expand(cp);
383                 else
384                         cp = _PATH_SENDMAIL;
385                 execv(cp, namelist);
386                 warn("%s", cp);
387                 _exit(1);
388         }
389         if (value("verbose") != NULL)
390                 (void)wait_child(pid);
391         else
392                 free_child(pid);
393 out:
394         (void)Fclose(mtf);
395 }
396
397 /*
398  * Fix the header by glopping all of the expanded names from
399  * the distribution list into the appropriate fields.
400  */
401 void
402 fixhead(struct header *hp, struct name *tolist)
403 {
404         struct name *np;
405
406         hp->h_to = NULL;
407         hp->h_cc = NULL;
408         hp->h_bcc = NULL;
409         for (np = tolist; np != NULL; np = np->n_flink) {
410                 /* Don't copy deleted addresses to the header */
411                 if (np->n_type & GDEL)
412                         continue;
413                 if ((np->n_type & GMASK) == GTO)
414                         hp->h_to =
415                             cat(hp->h_to, nalloc(np->n_name, np->n_type));
416                 else if ((np->n_type & GMASK) == GCC)
417                         hp->h_cc =
418                             cat(hp->h_cc, nalloc(np->n_name, np->n_type));
419                 else if ((np->n_type & GMASK) == GBCC)
420                         hp->h_bcc =
421                             cat(hp->h_bcc, nalloc(np->n_name, np->n_type));
422         }
423 }
424
425 /*
426  * Prepend a header in front of the collected stuff
427  * and return the new file.
428  */
429 FILE *
430 infix(struct header *hp, FILE *fi)
431 {
432         FILE *nfo, *nfi;
433         int c, fd;
434         char tempname[PATHSIZE];
435
436         (void)snprintf(tempname, sizeof(tempname),
437             "%s/mail.RsXXXXXXXXXX", tmpdir);
438         if ((fd = mkstemp(tempname)) == -1 ||
439             (nfo = Fdopen(fd, "w")) == NULL) {
440                 warn("%s", tempname);
441                 return (fi);
442         }
443         if ((nfi = Fopen(tempname, "r")) == NULL) {
444                 warn("%s", tempname);
445                 (void)Fclose(nfo);
446                 (void)rm(tempname);
447                 return (fi);
448         }
449         (void)rm(tempname);
450         (void)puthead(hp, nfo,
451             GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA);
452         c = getc(fi);
453         while (c != EOF) {
454                 (void)putc(c, nfo);
455                 c = getc(fi);
456         }
457         if (ferror(fi)) {
458                 warnx("read");
459                 rewind(fi);
460                 return (fi);
461         }
462         (void)fflush(nfo);
463         if (ferror(nfo)) {
464                 warn("%s", tempname);
465                 (void)Fclose(nfo);
466                 (void)Fclose(nfi);
467                 rewind(fi);
468                 return (fi);
469         }
470         (void)Fclose(nfo);
471         (void)Fclose(fi);
472         rewind(nfi);
473         return (nfi);
474 }
475
476 /*
477  * Dump the to, subject, cc header on the
478  * passed file buffer.
479  */
480 int
481 puthead(struct header *hp, FILE *fo, int w)
482 {
483         int gotcha;
484
485         gotcha = 0;
486         if (hp->h_to != NULL && w & GTO)
487                 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
488         if (hp->h_subject != NULL && w & GSUBJECT)
489                 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
490         if (hp->h_cc != NULL && w & GCC)
491                 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
492         if (hp->h_bcc != NULL && w & GBCC)
493                 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
494         if (hp->h_replyto != NULL && w & GREPLYTO)
495                 fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++;
496         if (hp->h_inreplyto != NULL && w & GINREPLYTO)
497                 fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), 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(const 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                         fprintf(fo, "\n    ");
522                         col = 4;
523                 } else
524                         fprintf(fo, " ");
525                 fputs(np->n_name, fo);
526                 if (comma)
527                         fprintf(fo, ",");
528                 col += len + comma;
529         }
530         fprintf(fo, "\n");
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         fprintf(fo, "\n");
555         (void)fflush(fo);
556         if (ferror(fo))
557                 warn("%s", name);
558         (void)Fclose(fo);
559         rewind(fi);
560         return (0);
561 }