]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - libexec/mail.local/mail.local.c
Return back vfork and use execve with TZ="" environment in vfork case
[FreeBSD/FreeBSD.git] / libexec / mail.local / mail.local.c
1 /*-
2  * Copyright (c) 1990, 1993, 1994
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  *      $Id: mail.local.c,v 1.15 1997/12/24 19:38:18 imp Exp $
34  */
35
36 #ifndef lint
37 static char copyright[] =
38 "@(#) Copyright (c) 1990, 1993, 1994\n\
39         The Regents of the University of California.  All rights reserved.\n";
40 #endif /* not lint */
41
42 #ifndef lint
43 static char sccsid[] = "@(#)mail.local.c        8.6 (Berkeley) 4/8/94";
44 #endif /* not lint */
45
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 #include <sys/socket.h>
49
50 #include <netinet/in.h>
51
52 #include <errno.h>
53 #include <fcntl.h>
54 #include <netdb.h>
55 #include <pwd.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <sysexits.h>
60 #include <syslog.h>
61 #include <time.h>
62 #include <unistd.h>
63
64 #if __STDC__
65 #include <stdarg.h>
66 #else
67 #include <varargs.h>
68 #endif
69
70 #include "pathnames.h"
71
72 int eval = EX_OK;                       /* sysexits.h error value. */
73
74 void            deliver __P((int, char *, int));
75 void            e_to_sys __P((int));
76 void            err __P((const char *, ...)) __dead2;
77 void            notifybiff __P((char *));
78 int             store __P((char *));
79 void            usage __P((void));
80 void            vwarn __P((const char *, _BSD_VA_LIST_));
81 void            warn __P((const char *, ...));
82
83 int
84 main(argc, argv)
85         int argc;
86         char *argv[];
87 {
88         struct passwd *pw;
89         int ch, fd, nobiff;
90         uid_t uid;
91         char *from;
92
93         openlog("mail.local", 0, LOG_MAIL);
94
95         from = NULL;
96         nobiff = 0;
97         while ((ch = getopt(argc, argv, "bdf:r:")) != -1)
98                 switch(ch) {
99                 case 'b':
100                         nobiff++;
101                         break;
102                 case 'd':               /* Backward compatible. */
103                         break;
104                 case 'f':
105                 case 'r':               /* Backward compatible. */
106                         if (from != NULL) {
107                                 warn("multiple -f options");
108                                 usage();
109                         }
110                         from = optarg;
111                         break;
112                 case '?':
113                 default:
114                         usage();
115                 }
116         argc -= optind;
117         argv += optind;
118
119         if (!*argv)
120                 usage();
121
122         /*
123          * If from not specified, use the name from getlogin() if the
124          * uid matches, otherwise, use the name from the password file
125          * corresponding to the uid.
126          */
127         uid = getuid();
128         if (!from && (!(from = getlogin()) ||
129             !(pw = getpwnam(from)) || pw->pw_uid != uid))
130                 from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
131
132         /*
133          * There is no way to distinguish the error status of one delivery
134          * from the rest of the deliveries.  So, if we failed hard on one
135          * or more deliveries, but had no failures on any of the others, we
136          * return a hard failure.  If we failed temporarily on one or more
137          * deliveries, we return a temporary failure regardless of the other
138          * failures.  This results in the delivery being reattempted later
139          * at the expense of repeated failures and multiple deliveries.
140          */
141         for (fd = store(from); *argv; ++argv)
142                 deliver(fd, *argv, nobiff);
143         exit(eval);
144 }
145
146 int
147 store(from)
148         char *from;
149 {
150         FILE *fp;
151         time_t tval;
152         int fd, eline;
153         char *tn, line[2048];
154
155         tn = strdup(_PATH_LOCTMP);
156         if ((fd = mkstemp(tn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
157                 e_to_sys(errno);
158                 err("unable to open temporary file");
159         }
160         (void)unlink(tn);
161         free(tn);
162
163         (void)time(&tval);
164         (void)fprintf(fp, "From %s %s", from, ctime(&tval));
165
166         line[0] = '\0';
167         for (eline = 1; fgets(line, sizeof(line), stdin);) {
168                 if (line[0] == '\n')
169                         eline = 1;
170                 else {
171                         if (eline && line[0] == 'F' &&
172                             !memcmp(line, "From ", 5))
173                                 (void)putc('>', fp);
174                         eline = 0;
175                 }
176                 (void)fprintf(fp, "%s", line);
177                 if (ferror(fp)) {
178                         e_to_sys(errno);
179                         err("temporary file write error");
180                 }
181         }
182
183         /* If message not newline terminated, need an extra. */
184         if (!strchr(line, '\n'))
185                 (void)putc('\n', fp);
186         /* Output a newline; note, empty messages are allowed. */
187         (void)putc('\n', fp);
188
189         if (fflush(fp) == EOF || ferror(fp)) {
190                 e_to_sys(errno);
191                 err("temporary file write error");
192         }
193         return (fd);
194 }
195
196 void
197 deliver(fd, name, nobiff)
198         int fd, nobiff;
199         char *name;
200 {
201         struct stat fsb, sb;
202         struct passwd *pw;
203         int mbfd, nr, nw, off;
204         char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
205         off_t curoff;
206         uid_t saveeuid;
207
208         /*
209          * Disallow delivery to unknown names -- special mailboxes can be
210          * handled in the sendmail aliases file.
211          */
212         if (!(pw = getpwnam(name))) {
213                 if (eval != EX_TEMPFAIL)
214                         eval = EX_UNAVAILABLE;
215                 warn("unknown name: %s", name);
216                 return;
217         }
218
219         (void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
220
221         /*
222          * If the mailbox is linked or a symlink, fail.  There's an obvious
223          * race here, that the file was replaced with a symbolic link after
224          * the lstat returned, but before the open.  We attempt to detect
225          * this by comparing the original stat information and information
226          * returned by an fstat of the file descriptor returned by the open.
227          *
228          * NB: this is a symptom of a larger problem, that the mail spooling
229          * directory is writeable by the wrong users.  If that directory is
230          * writeable, system security is compromised for other reasons, and
231          * it cannot be fixed here.
232          *
233          * If we created the mailbox, set the owner/group.  If that fails,
234          * just return.  Another process may have already opened it, so we
235          * can't unlink it.  Historically, binmail set the owner/group at
236          * each mail delivery.  We no longer do this, assuming that if the
237          * ownership or permissions were changed there was a reason.
238          *
239          * XXX
240          * open(2) should support flock'ing the file.
241          */
242         saveeuid=geteuid();
243
244 tryagain:
245         if (lstat(path, &sb)) {
246                 mbfd = open(path,
247                     O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
248                 if (mbfd == -1) {
249                         if (errno == EEXIST)
250                                 goto tryagain;
251                 } else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
252                         e_to_sys(errno);
253                         warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
254                         return;
255                 }
256
257                 /*
258                  * Now that the box is created and permissions are correct, we
259                  * close it and go back to the top so that we will come in
260                  * and write as the user.  We dont seteuid() before the above
261                  * open, because we have to be root/bin to write in var/mail 
262                  * -Crh (henrich@msu.edu)
263                  */
264                 close(mbfd);
265                 goto tryagain;
266         } else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
267                 e_to_sys(errno);
268                 warn("%s: linked file", path);
269                 return;
270         } else {
271                 /* Become the user, so quota enforcement will occur */
272                 if(seteuid(pw->pw_uid) != 0) {
273                         warn("Unable to seteuid()");
274                         return; 
275                 }
276
277                 mbfd = open(path, O_APPEND|O_WRONLY, 0);
278                 if (mbfd != -1 &&
279                     (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
280                     S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
281                     sb.st_ino != fsb.st_ino)) {
282                         warn("%s: file changed after open", path);
283                         (void)close(mbfd);
284                         seteuid(saveeuid); 
285                         return;
286                 }
287         }
288
289         if (mbfd == -1) {
290                 e_to_sys(errno);
291                 warn("%s: %s", path, strerror(errno));
292                 seteuid(saveeuid);
293                 return;
294         }
295
296         /* Wait until we can get a lock on the file. */
297         if (flock(mbfd, LOCK_EX)) {
298                 e_to_sys(errno);
299                 warn("%s: %s", path, strerror(errno));
300                 goto err1;
301         }
302
303         curoff = lseek(mbfd, (off_t)0, SEEK_END);
304         if (!nobiff) {
305                 (void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n",
306                                name, curoff);
307         }
308
309         /* Copy the message into the file. */
310         if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
311                 e_to_sys(errno);
312                 warn("temporary file: %s", strerror(errno));
313                 goto err1;
314         }
315         while ((nr = read(fd, buf, sizeof(buf))) > 0)
316                 for (off = 0; off < nr; nr -= nw, off += nw)
317                         if ((nw = write(mbfd, buf + off, nr)) < 0) {
318                                 e_to_sys(errno);
319                                 warn("%s: %s", path, strerror(errno));
320                                 goto err2;
321                         }
322         if (nr < 0) {
323                 e_to_sys(errno);
324                 warn("temporary file: %s", strerror(errno));
325 err2:           (void)ftruncate(mbfd, curoff);
326 err1:           (void)close(mbfd);
327                 seteuid(saveeuid);
328                 return;
329         }
330
331 #ifndef DONT_FSYNC
332         /* Flush to disk, don't wait for update. */
333         if (fsync(mbfd)) {
334                 e_to_sys(errno);
335                 warn("%s: %s", path, strerror(errno));
336                 goto err2;
337         }
338 #endif
339
340         /* Close and check -- NFS doesn't write until the close. */
341         if (close(mbfd)) {
342                 e_to_sys(errno);
343                 warn("%s: %s", path, strerror(errno));
344                 seteuid(saveeuid);
345                 return;
346         }
347
348         seteuid(saveeuid);
349
350         if (!nobiff)
351                 notifybiff(biffmsg);
352 }
353
354 void
355 notifybiff(msg)
356         char *msg;
357 {
358         static struct sockaddr_in addr;
359         static int f = -1;
360         struct hostent *hp;
361         struct servent *sp;
362         int len;
363
364         if (!addr.sin_family) {
365                 /* Be silent if biff service not available. */
366                 if (!(sp = getservbyname("biff", "udp")))
367                         return;
368                 if (!(hp = gethostbyname("localhost"))) {
369                         warn("localhost: %s", strerror(errno));
370                         return;
371                 }
372                 addr.sin_family = hp->h_addrtype;
373                 memmove(&addr.sin_addr, hp->h_addr, 
374                     MIN(hp->h_length,sizeof(addr.sin_addr)));
375                 addr.sin_port = sp->s_port;
376         }
377         if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
378                 warn("socket: %s", strerror(errno));
379                 return;
380         }
381         len = strlen(msg) + 1;
382         if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
383             != len)
384                 warn("sendto biff: %s", strerror(errno));
385 }
386
387 void
388 usage()
389 {
390         eval = EX_USAGE;
391         err("usage: mail.local [-b] [-f from] user ...");
392 }
393
394 #if __STDC__
395 void
396 err(const char *fmt, ...)
397 #else
398 void
399 err(fmt, va_alist)
400         const char *fmt;
401         va_dcl
402 #endif
403 {
404         va_list ap;
405
406 #if __STDC__
407         va_start(ap, fmt);
408 #else
409         va_start(ap);
410 #endif
411         vwarn(fmt, ap);
412         va_end(ap);
413
414         exit(eval);
415 }
416
417 void
418 #if __STDC__
419 warn(const char *fmt, ...)
420 #else
421 warn(fmt, va_alist)
422         const char *fmt;
423         va_dcl
424 #endif
425 {
426         va_list ap;
427
428 #if __STDC__
429         va_start(ap, fmt);
430 #else
431         va_start(ap);
432 #endif
433         vwarn(fmt, ap);
434         va_end(ap);
435 }
436
437 void
438 vwarn(fmt, ap)
439         const char *fmt;
440         _BSD_VA_LIST_ ap;
441 {
442         /*
443          * Log the message to stderr.
444          *
445          * Don't use LOG_PERROR as an openlog() flag to do this,
446          * it's not portable enough.
447          */
448         if (eval != EX_USAGE)
449                 (void)fprintf(stderr, "mail.local: ");
450         (void)vfprintf(stderr, fmt, ap);
451         (void)fprintf(stderr, "\n");
452
453         /* Log the message to syslog. */
454         vsyslog(LOG_ERR, fmt, ap);
455 }
456
457 /*
458  * e_to_sys --
459  *      Guess which errno's are temporary.  Gag me.
460  */
461 void
462 e_to_sys(num)
463         int num;
464 {
465         /* Temporary failures override hard errors. */
466         if (eval == EX_TEMPFAIL)
467                 return;
468
469         switch(num) {           /* Hopefully temporary errors. */
470 #ifdef EAGAIN
471         case EAGAIN:            /* Resource temporarily unavailable */
472 #endif
473 #ifdef EDQUOT
474         case EDQUOT:            /* Disc quota exceeded */
475 #endif
476 #ifdef EBUSY
477         case EBUSY:             /* Device busy */
478 #endif
479 #ifdef EPROCLIM
480         case EPROCLIM:          /* Too many processes */
481 #endif
482 #ifdef EUSERS
483         case EUSERS:            /* Too many users */
484 #endif
485 #ifdef ECONNABORTED
486         case ECONNABORTED:      /* Software caused connection abort */
487 #endif
488 #ifdef ECONNREFUSED
489         case ECONNREFUSED:      /* Connection refused */
490 #endif
491 #ifdef ECONNRESET
492         case ECONNRESET:        /* Connection reset by peer */
493 #endif
494 #ifdef EDEADLK
495         case EDEADLK:           /* Resource deadlock avoided */
496 #endif
497 #ifdef EFBIG
498         case EFBIG:             /* File too large */
499 #endif
500 #ifdef EHOSTDOWN
501         case EHOSTDOWN:         /* Host is down */
502 #endif
503 #ifdef EHOSTUNREACH
504         case EHOSTUNREACH:      /* No route to host */
505 #endif
506 #ifdef EMFILE
507         case EMFILE:            /* Too many open files */
508 #endif
509 #ifdef ENETDOWN
510         case ENETDOWN:          /* Network is down */
511 #endif
512 #ifdef ENETRESET
513         case ENETRESET:         /* Network dropped connection on reset */
514 #endif
515 #ifdef ENETUNREACH
516         case ENETUNREACH:       /* Network is unreachable */
517 #endif
518 #ifdef ENFILE
519         case ENFILE:            /* Too many open files in system */
520 #endif
521 #ifdef ENOBUFS
522         case ENOBUFS:           /* No buffer space available */
523 #endif
524 #ifdef ENOMEM
525         case ENOMEM:            /* Cannot allocate memory */
526 #endif
527 #ifdef ENOSPC
528         case ENOSPC:            /* No space left on device */
529 #endif
530 #ifdef EROFS
531         case EROFS:             /* Read-only file system */
532 #endif
533 #ifdef ESTALE
534         case ESTALE:            /* Stale NFS file handle */
535 #endif
536 #ifdef ETIMEDOUT
537         case ETIMEDOUT:         /* Connection timed out */
538 #endif
539 #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
540         case EWOULDBLOCK:       /* Operation would block. */
541 #endif
542                 eval = EX_TEMPFAIL;
543                 break;
544         default:
545                 eval = EX_UNAVAILABLE;
546                 break;
547         }
548 }