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