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