145976Sbostic /*- 245976Sbostic * Copyright (c) 1990 The Regents of the University of California. 345976Sbostic * All rights reserved. 445976Sbostic * 545976Sbostic * %sccs.include.redist.c% 645976Sbostic */ 745976Sbostic 814465Ssam #ifndef lint 945976Sbostic char copyright[] = 1045976Sbostic "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 1145976Sbostic All rights reserved.\n"; 1245976Sbostic #endif /* not lint */ 1314465Ssam 1445976Sbostic #ifndef lint 15*60097Sbostic static char sccsid[] = "@(#)mail.local.c 5.13 (Berkeley) 05/17/93"; 1645976Sbostic #endif /* not lint */ 1745976Sbostic 1829763Skarels #include <sys/param.h> 1916731Sralph #include <sys/stat.h> 2045976Sbostic #include <sys/socket.h> 2157648Sbostic 2245976Sbostic #include <netinet/in.h> 2357648Sbostic 2457648Sbostic #include <errno.h> 2546672Sbostic #include <fcntl.h> 2645976Sbostic #include <netdb.h> 2745976Sbostic #include <pwd.h> 28579Sroot #include <stdio.h> 2946672Sbostic #include <stdlib.h> 3045976Sbostic #include <string.h> 3159601Sbostic #include <sysexits.h> 3257648Sbostic #include <syslog.h> 3357648Sbostic #include <time.h> 3457648Sbostic #include <unistd.h> 3557648Sbostic 3659601Sbostic #if __STDC__ 3759601Sbostic #include <stdarg.h> 3859601Sbostic #else 3959601Sbostic #include <varargs.h> 4059601Sbostic #endif 4159601Sbostic 4237465Sbostic #include "pathnames.h" 43579Sroot 4459601Sbostic int eval = EX_OK; /* sysexits.h error value. */ 45579Sroot 4659601Sbostic void deliver __P((int, char *)); 4759601Sbostic void e_to_sys __P((int)); 4859601Sbostic __dead void err __P((const char *, ...)); 4959601Sbostic void notifybiff __P((char *)); 5059601Sbostic int store __P((char *)); 5159601Sbostic void usage __P((void)); 5259601Sbostic void vwarn __P((const char *, _BSD_VA_LIST_)); 5359601Sbostic void warn __P((const char *, ...)); 5450092Sbostic 5557648Sbostic int 56579Sroot main(argc, argv) 5745976Sbostic int argc; 5859601Sbostic char *argv[]; 59579Sroot { 6045976Sbostic struct passwd *pw; 6159601Sbostic int ch, fd; 6245976Sbostic uid_t uid; 6345976Sbostic char *from; 64579Sroot 6558400Sbostic openlog("mail.local", 0, LOG_MAIL); 6616731Sralph 6745976Sbostic from = NULL; 6845976Sbostic while ((ch = getopt(argc, argv, "df:r:")) != EOF) 6945976Sbostic switch(ch) { 7059601Sbostic case 'd': /* Backward compatible. */ 7116731Sralph break; 7216731Sralph case 'f': 7359601Sbostic case 'r': /* Backward compatible. */ 7459601Sbostic if (from != NULL) { 7559601Sbostic warn("multiple -f options"); 7659601Sbostic usage(); 7759601Sbostic } 7845976Sbostic from = optarg; 79579Sroot break; 8045976Sbostic case '?': 8116731Sralph default: 8245976Sbostic usage(); 8316731Sralph } 8445976Sbostic argc -= optind; 8545976Sbostic argv += optind; 86579Sroot 8745976Sbostic if (!*argv) 8845976Sbostic usage(); 89579Sroot 9045976Sbostic /* 9145976Sbostic * If from not specified, use the name from getlogin() if the 9245976Sbostic * uid matches, otherwise, use the name from the password file 9345976Sbostic * corresponding to the uid. 9445976Sbostic */ 9545976Sbostic uid = getuid(); 9646034Sbostic if (!from && (!(from = getlogin()) || 9746034Sbostic !(pw = getpwnam(from)) || pw->pw_uid != uid)) 9845976Sbostic from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 99579Sroot 10059601Sbostic /* 10159601Sbostic * There is no way to distinguish the error status of one delivery 10259601Sbostic * from the rest of the deliveries. So, if we failed hard on one 10359601Sbostic * or more deliveries, but had no failures on any of the others, we 10459601Sbostic * return a hard failure. If we failed temporarily on one or more 10559601Sbostic * deliveries, we return a temporary failure regardless of the other 10659601Sbostic * failures. This results in the delivery being reattempted later 10759601Sbostic * at the expense of repeated failures and multiple deliveries. 10859601Sbostic */ 10959601Sbostic for (fd = store(from); *argv; ++argv) 11059601Sbostic deliver(fd, *argv); 11145976Sbostic exit(eval); 112579Sroot } 113579Sroot 11457648Sbostic int 11545976Sbostic store(from) 11645976Sbostic char *from; 117579Sroot { 11845976Sbostic FILE *fp; 11945976Sbostic time_t tval; 12045976Sbostic int fd, eline; 12145976Sbostic char *tn, line[2048]; 122579Sroot 12347075Sdonn tn = strdup(_PATH_LOCTMP); 12459601Sbostic if ((fd = mkstemp(tn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { 12559601Sbostic e_to_sys(errno); 12659601Sbostic err("unable to open temporary file"); 12759601Sbostic } 12845976Sbostic (void)unlink(tn); 12947075Sdonn free(tn); 130579Sroot 13145976Sbostic (void)time(&tval); 13245976Sbostic (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 133579Sroot 13445976Sbostic line[0] = '\0'; 13545976Sbostic for (eline = 1; fgets(line, sizeof(line), stdin);) { 13645976Sbostic if (line[0] == '\n') 13745976Sbostic eline = 1; 13845976Sbostic else { 139*60097Sbostic if (eline && line[0] == 'F' && 140*60097Sbostic !memcmp(line, "From ", 5)) 14145976Sbostic (void)putc('>', fp); 14245976Sbostic eline = 0; 14345976Sbostic } 14445976Sbostic (void)fprintf(fp, "%s", line); 14559601Sbostic if (ferror(fp)) { 14659601Sbostic e_to_sys(errno); 14759601Sbostic err("temporary file write error"); 14859601Sbostic } 149579Sroot } 150579Sroot 15145976Sbostic /* If message not newline terminated, need an extra. */ 15259601Sbostic if (!strchr(line, '\n')) 15345976Sbostic (void)putc('\n', fp); 15445976Sbostic /* Output a newline; note, empty messages are allowed. */ 15545976Sbostic (void)putc('\n', fp); 15611893Seric 15759601Sbostic if (fflush(fp) == EOF || ferror(fp)) { 15859601Sbostic e_to_sys(errno); 15959601Sbostic err("temporary file write error"); 16059601Sbostic } 16159601Sbostic return (fd); 162579Sroot } 163579Sroot 16459601Sbostic void 16545976Sbostic deliver(fd, name) 16645976Sbostic int fd; 16745976Sbostic char *name; 168579Sroot { 16945976Sbostic struct stat sb; 17045976Sbostic struct passwd *pw; 17159601Sbostic int mbfd, nr, nw, off; 17245976Sbostic char biffmsg[100], buf[8*1024], path[MAXPATHLEN]; 17354203Sbostic off_t curoff; 174579Sroot 17545976Sbostic /* 17645976Sbostic * Disallow delivery to unknown names -- special mailboxes can be 17745976Sbostic * handled in the sendmail aliases file. 17845976Sbostic */ 17945976Sbostic if (!(pw = getpwnam(name))) { 18059601Sbostic if (eval != EX_TEMPFAIL) 18159601Sbostic eval = EX_UNAVAILABLE; 18259601Sbostic warn("unknown name: %s", name); 18359601Sbostic return; 18445976Sbostic } 185579Sroot 18657648Sbostic (void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name); 187579Sroot 188579Sroot /* 18959601Sbostic * If the mailbox is a linked or a symlink, fail. 19059601Sbostic * 19159601Sbostic * If we created the mailbox, set the owner/group. If that fails, 19259601Sbostic * just return. Another process may have already opened it, so we 19359601Sbostic * can't unlink it. Historically, binmail set the owner/group at 19459601Sbostic * each mail delivery. We no longer do this, assuming that if the 19559601Sbostic * ownership or permissions were changed there was a reason. 19659601Sbostic * 19759601Sbostic * XXX 19859601Sbostic * open(2) should support flock'ing the file. 199579Sroot */ 20059601Sbostic if (lstat(path, &sb)) { 20159601Sbostic if ((mbfd = open(path, 20259601Sbostic O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) 20359601Sbostic mbfd = open(path, O_APPEND|O_WRONLY, 0); 20459601Sbostic else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) { 20559601Sbostic e_to_sys(errno); 20659601Sbostic warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name); 20759601Sbostic return; 20859601Sbostic } 20959601Sbostic } else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) { 21059601Sbostic e_to_sys(errno); 21159601Sbostic warn("%s: linked file", path); 21259601Sbostic return; 21359601Sbostic } else 21459601Sbostic mbfd = open(path, O_APPEND|O_WRONLY, 0); 21559601Sbostic 21659601Sbostic if (mbfd == -1) { 21759601Sbostic e_to_sys(errno); 21859601Sbostic warn("%s: %s", path, strerror(errno)); 21959601Sbostic return; 22045976Sbostic } 221579Sroot 22259601Sbostic /* Wait until we can get a lock on the file. */ 22345976Sbostic if (flock(mbfd, LOCK_EX)) { 22459601Sbostic e_to_sys(errno); 22559601Sbostic warn("%s: %s", path, strerror(errno)); 22659601Sbostic goto err1; 22745976Sbostic } 228579Sroot 22959601Sbostic /* Get the starting offset of the new message for biff. */ 23054203Sbostic curoff = lseek(mbfd, (off_t)0, SEEK_END); 23157648Sbostic (void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n", name, curoff); 23259601Sbostic 23359601Sbostic /* Copy the message into the file. */ 23454203Sbostic if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) { 23559601Sbostic e_to_sys(errno); 23659601Sbostic warn("temporary file: %s", strerror(errno)); 23759601Sbostic goto err1; 23845976Sbostic } 23945976Sbostic while ((nr = read(fd, buf, sizeof(buf))) > 0) 24045976Sbostic for (off = 0; off < nr; nr -= nw, off += nw) 24145976Sbostic if ((nw = write(mbfd, buf + off, nr)) < 0) { 24259601Sbostic e_to_sys(errno); 24359601Sbostic warn("%s: %s", path, strerror(errno)); 24459601Sbostic goto err2;; 24545976Sbostic } 24645976Sbostic if (nr < 0) { 24759601Sbostic e_to_sys(errno); 24859601Sbostic warn("temporary file: %s", strerror(errno)); 24959601Sbostic goto err2;; 250579Sroot } 251579Sroot 25259601Sbostic /* Flush to disk, don't wait for update. */ 25359601Sbostic if (fsync(mbfd)) { 25459601Sbostic e_to_sys(errno); 25559601Sbostic warn("%s: %s", path, strerror(errno)); 25659601Sbostic err2: (void)ftruncate(mbfd, curoff); 25759601Sbostic err1: (void)close(mbfd); 25859601Sbostic return; 25959601Sbostic } 26059601Sbostic 26159601Sbostic /* Close and check -- NFS doesn't write until the close. */ 26259601Sbostic if (close(mbfd)) { 26359601Sbostic e_to_sys(errno); 26459601Sbostic warn("%s: %s", path, strerror(errno)); 26559601Sbostic return; 26659601Sbostic } 267579Sroot 26859601Sbostic notifybiff(biffmsg); 269579Sroot } 270579Sroot 27150092Sbostic void 27216731Sralph notifybiff(msg) 27316731Sralph char *msg; 27416731Sralph { 27516731Sralph static struct sockaddr_in addr; 27616731Sralph static int f = -1; 27745976Sbostic struct hostent *hp; 27845976Sbostic struct servent *sp; 27945976Sbostic int len; 28016731Sralph 28145976Sbostic if (!addr.sin_family) { 28245976Sbostic /* Be silent if biff service not available. */ 28345976Sbostic if (!(sp = getservbyname("biff", "udp"))) 28445976Sbostic return; 28545976Sbostic if (!(hp = gethostbyname("localhost"))) { 28659601Sbostic warn("localhost: %s", strerror(errno)); 28745976Sbostic return; 28816731Sralph } 28945976Sbostic addr.sin_family = hp->h_addrtype; 290*60097Sbostic memmove(&addr.sin_addr, hp->h_addr, hp->h_length); 29145976Sbostic addr.sin_port = sp->s_port; 29216731Sralph } 29345976Sbostic if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 29459601Sbostic warn("socket: %s", strerror(errno)); 29545976Sbostic return; 29616731Sralph } 29745976Sbostic len = strlen(msg) + 1; 29846672Sbostic if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 29946672Sbostic != len) 30059601Sbostic warn("sendto biff: %s", strerror(errno)); 30116731Sralph } 30216731Sralph 30350092Sbostic void 30445976Sbostic usage() 305579Sroot { 30659601Sbostic eval = EX_USAGE; 30759601Sbostic err("usage: mail.local [-f from] user ..."); 308579Sroot } 309579Sroot 31059601Sbostic void 31150092Sbostic #if __STDC__ 31259601Sbostic err(const char *fmt, ...) 31350092Sbostic #else 31459601Sbostic err(fmt, va_alist) 31559601Sbostic const char *fmt; 31659601Sbostic va_dcl 31750092Sbostic #endif 31859601Sbostic { 31959601Sbostic va_list ap; 32050092Sbostic 32159601Sbostic #if __STDC__ 32259601Sbostic va_start(ap, fmt); 32359601Sbostic #else 32459601Sbostic va_start(ap); 32559601Sbostic #endif 32659601Sbostic vwarn(fmt, ap); 32759601Sbostic va_end(ap); 32859601Sbostic 32959601Sbostic exit(eval); 33059601Sbostic } 33159601Sbostic 33250092Sbostic void 33350092Sbostic #if __STDC__ 33459601Sbostic warn(const char *fmt, ...) 33550092Sbostic #else 33659601Sbostic warn(fmt, va_alist) 33759601Sbostic const char *fmt; 33850092Sbostic va_dcl 33950092Sbostic #endif 340579Sroot { 34145976Sbostic va_list ap; 34259601Sbostic 34350092Sbostic #if __STDC__ 34446554Sbostic va_start(ap, fmt); 34550092Sbostic #else 34650092Sbostic va_start(ap); 34750092Sbostic #endif 34859601Sbostic vwarn(fmt, ap); 34959601Sbostic va_end(ap); 35059601Sbostic } 35159601Sbostic 35259601Sbostic void 35359601Sbostic vwarn(fmt, ap) 35459601Sbostic const char *fmt; 35559601Sbostic _BSD_VA_LIST_ ap; 35659601Sbostic { 35758400Sbostic /* 35859601Sbostic * Log the message to stderr. 35959601Sbostic * 36059601Sbostic * Don't use LOG_PERROR as an openlog() flag to do this, 36159601Sbostic * it's not portable enough. 36258400Sbostic */ 36359601Sbostic if (eval != EX_USAGE) 36459601Sbostic (void)fprintf(stderr, "mail.local: "); 36558396Sbostic (void)vfprintf(stderr, fmt, ap); 36658400Sbostic (void)fprintf(stderr, "\n"); 36759601Sbostic 36859601Sbostic /* Log the message to syslog. */ 36945976Sbostic vsyslog(LOG_ERR, fmt, ap); 370579Sroot } 37159601Sbostic 37259601Sbostic /* 37359601Sbostic * e_to_sys -- 37459601Sbostic * Guess which errno's are temporary. Gag me. 37559601Sbostic */ 37659601Sbostic void 37759601Sbostic e_to_sys(num) 37859601Sbostic int num; 37959601Sbostic { 38059601Sbostic /* Temporary failures override hard errors. */ 38159601Sbostic if (eval == EX_TEMPFAIL) 38259601Sbostic return; 38359601Sbostic 38459601Sbostic switch(num) { /* Hopefully temporary errors. */ 38559601Sbostic #ifdef EAGAIN 38659601Sbostic case EAGAIN: /* Resource temporarily unavailable */ 38759601Sbostic #endif 38859601Sbostic #ifdef EDQUOT 38959601Sbostic case EDQUOT: /* Disc quota exceeded */ 39059601Sbostic #endif 39159601Sbostic #ifdef EBUSY 39259601Sbostic case EBUSY: /* Device busy */ 39359601Sbostic #endif 39459601Sbostic #ifdef EPROCLIM 39559601Sbostic case EPROCLIM: /* Too many processes */ 39659601Sbostic #endif 39759601Sbostic #ifdef EUSERS 39859601Sbostic case EUSERS: /* Too many users */ 39959601Sbostic #endif 40059601Sbostic #ifdef ECONNABORTED 40159601Sbostic case ECONNABORTED: /* Software caused connection abort */ 40259601Sbostic #endif 40359601Sbostic #ifdef ECONNREFUSED 40459601Sbostic case ECONNREFUSED: /* Connection refused */ 40559601Sbostic #endif 40659601Sbostic #ifdef ECONNRESET 40759601Sbostic case ECONNRESET: /* Connection reset by peer */ 40859601Sbostic #endif 40959601Sbostic #ifdef EDEADLK 41059601Sbostic case EDEADLK: /* Resource deadlock avoided */ 41159601Sbostic #endif 41259601Sbostic #ifdef EFBIG 41359601Sbostic case EFBIG: /* File too large */ 41459601Sbostic #endif 41559601Sbostic #ifdef EHOSTDOWN 41659601Sbostic case EHOSTDOWN: /* Host is down */ 41759601Sbostic #endif 41859601Sbostic #ifdef EHOSTUNREACH 41959601Sbostic case EHOSTUNREACH: /* No route to host */ 42059601Sbostic #endif 42159601Sbostic #ifdef EMFILE 42259601Sbostic case EMFILE: /* Too many open files */ 42359601Sbostic #endif 42459601Sbostic #ifdef ENETDOWN 42559601Sbostic case ENETDOWN: /* Network is down */ 42659601Sbostic #endif 42759601Sbostic #ifdef ENETRESET 42859601Sbostic case ENETRESET: /* Network dropped connection on reset */ 42959601Sbostic #endif 43059601Sbostic #ifdef ENETUNREACH 43159601Sbostic case ENETUNREACH: /* Network is unreachable */ 43259601Sbostic #endif 43359601Sbostic #ifdef ENFILE 43459601Sbostic case ENFILE: /* Too many open files in system */ 43559601Sbostic #endif 43659601Sbostic #ifdef ENOBUFS 43759601Sbostic case ENOBUFS: /* No buffer space available */ 43859601Sbostic #endif 43959601Sbostic #ifdef ENOMEM 44059601Sbostic case ENOMEM: /* Cannot allocate memory */ 44159601Sbostic #endif 44259601Sbostic #ifdef ENOSPC 44359601Sbostic case ENOSPC: /* No space left on device */ 44459601Sbostic #endif 44559601Sbostic #ifdef EROFS 44659601Sbostic case EROFS: /* Read-only file system */ 44759601Sbostic #endif 44859601Sbostic #ifdef ESTALE 44959601Sbostic case ESTALE: /* Stale NFS file handle */ 45059601Sbostic #endif 45159601Sbostic #ifdef ETIMEDOUT 45259601Sbostic case ETIMEDOUT: /* Connection timed out */ 45359601Sbostic #endif 45459601Sbostic #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) 45559601Sbostic case EWOULDBLOCK: /* Operation would block. */ 45659601Sbostic #endif 45759601Sbostic eval = EX_TEMPFAIL; 45859601Sbostic break; 45959601Sbostic default: 46059601Sbostic eval = EX_UNAVAILABLE; 46159601Sbostic break; 46259601Sbostic } 46359601Sbostic } 464