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