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