]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.bin/mail/send.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.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  * 4. Neither the name of the University nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #ifndef lint
31 #if 0
32 static char sccsid[] = "@(#)send.c      8.1 (Berkeley) 6/6/93";
33 #endif
34 #endif /* not lint */
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
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         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                         (void)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, and
348                  * we would like to save message in a file named by the first
349                  * recipient the user has entered, not the one being the first
350                  * 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 saved 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                         (void)savemail(expand(nbuf), mtf);
380                 free(nbuf);
381                 free(nsto);
382         } else if ((cp = value("record")) != NULL)
383                 (void)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                 (void)sigemptyset(&nset);
398                 (void)sigaddset(&nset, SIGHUP);
399                 (void)sigaddset(&nset, SIGINT);
400                 (void)sigaddset(&nset, SIGQUIT);
401                 (void)sigaddset(&nset, SIGTSTP);
402                 (void)sigaddset(&nset, SIGTTIN);
403                 (void)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                 (void)wait_child(pid);
415         else
416                 free_child(pid);
417 out:
418         (void)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         (void)snprintf(tempname, sizeof(tempname),
461             "%s/mail.RsXXXXXXXXXX", tmpdir);
462         if ((fd = mkstemp(tempname)) == -1 ||
463             (nfo = Fdopen(fd, "w")) == NULL) {
464                 warn("%s", tempname);
465                 return (fi);
466         }
467         if ((nfi = Fopen(tempname, "r")) == NULL) {
468                 warn("%s", tempname);
469                 (void)Fclose(nfo);
470                 (void)rm(tempname);
471                 return (fi);
472         }
473         (void)rm(tempname);
474         (void)puthead(hp, nfo,
475             GTO|GSUBJECT|GCC|GBCC|GREPLYTO|GINREPLYTO|GNL|GCOMMA);
476         c = getc(fi);
477         while (c != EOF) {
478                 (void)putc(c, nfo);
479                 c = getc(fi);
480         }
481         if (ferror(fi)) {
482                 warnx("read");
483                 rewind(fi);
484                 return (fi);
485         }
486         (void)fflush(nfo);
487         if (ferror(nfo)) {
488                 warn("%s", tempname);
489                 (void)Fclose(nfo);
490                 (void)Fclose(nfi);
491                 rewind(fi);
492                 return (fi);
493         }
494         (void)Fclose(nfo);
495         (void)Fclose(fi);
496         rewind(nfi);
497         return (nfi);
498 }
499
500 /*
501  * Dump the to, subject, cc header on the
502  * passed file buffer.
503  */
504 int
505 puthead(struct header *hp, FILE *fo, int w)
506 {
507         int gotcha;
508
509         gotcha = 0;
510         if (hp->h_to != NULL && w & GTO)
511                 fmt("To:", hp->h_to, fo, w&GCOMMA), gotcha++;
512         if (hp->h_subject != NULL && w & GSUBJECT)
513                 fprintf(fo, "Subject: %s\n", hp->h_subject), gotcha++;
514         if (hp->h_cc != NULL && w & GCC)
515                 fmt("Cc:", hp->h_cc, fo, w&GCOMMA), gotcha++;
516         if (hp->h_bcc != NULL && w & GBCC)
517                 fmt("Bcc:", hp->h_bcc, fo, w&GCOMMA), gotcha++;
518         if (hp->h_replyto != NULL && w & GREPLYTO)
519                 fprintf(fo, "Reply-To: %s\n", hp->h_replyto), gotcha++;
520         if (hp->h_inreplyto != NULL && w & GINREPLYTO)
521                 fprintf(fo, "In-Reply-To: <%s>\n", hp->h_inreplyto), gotcha++;
522         if (gotcha && w & GNL)
523                 (void)putc('\n', fo);
524         return (0);
525 }
526
527 /*
528  * Format the given header line to not exceed 72 characters.
529  */
530 void
531 fmt(const char *str, struct name *np, FILE *fo, int comma)
532 {
533         int col, len;
534
535         comma = comma ? 1 : 0;
536         col = strlen(str);
537         if (col)
538                 fputs(str, fo);
539         for (; np != NULL; np = np->n_flink) {
540                 if (np->n_flink == NULL)
541                         comma = 0;
542                 len = strlen(np->n_name);
543                 col++;          /* for the space */
544                 if (col + len + comma > 72 && col > 4) {
545                         fprintf(fo, "\n    ");
546                         col = 4;
547                 } else
548                         fprintf(fo, " ");
549                 fputs(np->n_name, fo);
550                 if (comma)
551                         fprintf(fo, ",");
552                 col += len + comma;
553         }
554         fprintf(fo, "\n");
555 }
556
557 /*
558  * Save the outgoing mail on the passed file.
559  */
560
561 /*ARGSUSED*/
562 int
563 savemail(char name[], FILE *fi)
564 {
565         FILE *fo;
566         char buf[BUFSIZ];
567         int i;
568         time_t now;
569
570         if ((fo = Fopen(name, "a")) == NULL) {
571                 warn("%s", name);
572                 return (-1);
573         }
574         (void)time(&now);
575         fprintf(fo, "From %s %s", myname, ctime(&now));
576         while ((i = fread(buf, 1, sizeof(buf), fi)) > 0)
577                 (void)fwrite(buf, 1, i, fo);
578         fprintf(fo, "\n");
579         (void)fflush(fo);
580         if (ferror(fo))
581                 warn("%s", name);
582         (void)Fclose(fo);
583         rewind(fi);
584         return (0);
585 }