145976Sbostic /*-
2*66733Sbostic  * Copyright (c) 1990, 1993, 1994
361434Sbostic  *	The Regents of the University of California.  All rights reserved.
445976Sbostic  *
545976Sbostic  * %sccs.include.redist.c%
645976Sbostic  */
745976Sbostic 
814465Ssam #ifndef lint
961434Sbostic static char copyright[] =
10*66733Sbostic "@(#) Copyright (c) 1990, 1993, 1994\n\
1161434Sbostic 	The Regents of the University of California.  All rights reserved.\n";
1245976Sbostic #endif /* not lint */
1314465Ssam 
1445976Sbostic #ifndef lint
15*66733Sbostic static char sccsid[] = "@(#)mail.local.c	8.4 (Berkeley) 04/08/94";
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 {
13960097Sbostic 			if (eline && line[0] == 'F' &&
14060097Sbostic 			    !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 	/*
189*66733Sbostic 	 * If the mailbox is linked or a symlink, fail.  There's an obvious
190*66733Sbostic 	 * race here, that the file was replaced with a symbolic link after
191*66733Sbostic 	 * the lstat returned, but before the open.  Mail.local cannot detect
192*66733Sbostic 	 * this race.  Furthermore, it's a symptom of a larger problem, that
193*66733Sbostic 	 * the mail spooling directory is writeable by the wrong users.  NB:
194*66733Sbostic 	 * if that directory is writeable, system security is compromised for
195*66733Sbostic 	 * other reasons, and it cannot be fixed here.  Be that as it may, we
196*66733Sbostic 	 * we make the race harder by checking after the open as well.
19759601Sbostic 	 *
19859601Sbostic 	 * If we created the mailbox, set the owner/group.  If that fails,
19959601Sbostic 	 * just return.  Another process may have already opened it, so we
20059601Sbostic 	 * can't unlink it.  Historically, binmail set the owner/group at
20159601Sbostic 	 * each mail delivery.  We no longer do this, assuming that if the
20259601Sbostic 	 * ownership or permissions were changed there was a reason.
20359601Sbostic 	 *
20459601Sbostic 	 * XXX
20559601Sbostic 	 * open(2) should support flock'ing the file.
206579Sroot 	 */
20759601Sbostic 	if (lstat(path, &sb)) {
20859601Sbostic 		if ((mbfd = open(path,
20959601Sbostic 		    O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR)) < 0)
21059601Sbostic 			mbfd = open(path, O_APPEND|O_WRONLY, 0);
21159601Sbostic 		else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
21259601Sbostic 			e_to_sys(errno);
21359601Sbostic 			warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
21459601Sbostic 			return;
21559601Sbostic 		}
21659601Sbostic 	} else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
21759601Sbostic 		e_to_sys(errno);
21859601Sbostic 		warn("%s: linked file", path);
21959601Sbostic 		return;
220*66733Sbostic 	} else {
22159601Sbostic 		mbfd = open(path, O_APPEND|O_WRONLY, 0);
222*66733Sbostic 		if (mbfd != -1 && (lstat(path, &sb) ||
223*66733Sbostic 		    sb.st_nlink != 1 || S_ISLNK(sb.st_mode))) {
224*66733Sbostic 			e_to_sys(errno);
225*66733Sbostic 			warn("%s: missing or linked file", path);
226*66733Sbostic 			(void)close(mbfd);
227*66733Sbostic 			return;
228*66733Sbostic 		}
229*66733Sbostic 	}
23059601Sbostic 
23159601Sbostic 	if (mbfd == -1) {
23259601Sbostic 		e_to_sys(errno);
23359601Sbostic 		warn("%s: %s", path, strerror(errno));
23459601Sbostic 		return;
23545976Sbostic 	}
236579Sroot 
23759601Sbostic 	/* Wait until we can get a lock on the file. */
23845976Sbostic 	if (flock(mbfd, LOCK_EX)) {
23959601Sbostic 		e_to_sys(errno);
24059601Sbostic 		warn("%s: %s", path, strerror(errno));
24159601Sbostic 		goto err1;
24245976Sbostic 	}
243579Sroot 
24459601Sbostic 	/* Get the starting offset of the new message for biff. */
24554203Sbostic 	curoff = lseek(mbfd, (off_t)0, SEEK_END);
24657648Sbostic 	(void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n", name, curoff);
24759601Sbostic 
24859601Sbostic 	/* Copy the message into the file. */
24954203Sbostic 	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
25059601Sbostic 		e_to_sys(errno);
25159601Sbostic 		warn("temporary file: %s", strerror(errno));
25259601Sbostic 		goto err1;
25345976Sbostic 	}
25445976Sbostic 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
25545976Sbostic 		for (off = 0; off < nr; nr -= nw, off += nw)
25645976Sbostic 			if ((nw = write(mbfd, buf + off, nr)) < 0) {
25759601Sbostic 				e_to_sys(errno);
25859601Sbostic 				warn("%s: %s", path, strerror(errno));
25959601Sbostic 				goto err2;;
26045976Sbostic 			}
26145976Sbostic 	if (nr < 0) {
26259601Sbostic 		e_to_sys(errno);
26359601Sbostic 		warn("temporary file: %s", strerror(errno));
26459601Sbostic 		goto err2;;
265579Sroot 	}
266579Sroot 
26759601Sbostic 	/* Flush to disk, don't wait for update. */
26859601Sbostic 	if (fsync(mbfd)) {
26959601Sbostic 		e_to_sys(errno);
27059601Sbostic 		warn("%s: %s", path, strerror(errno));
27159601Sbostic err2:		(void)ftruncate(mbfd, curoff);
27259601Sbostic err1:		(void)close(mbfd);
27359601Sbostic 		return;
27459601Sbostic 	}
27559601Sbostic 
27659601Sbostic 	/* Close and check -- NFS doesn't write until the close. */
27759601Sbostic 	if (close(mbfd)) {
27859601Sbostic 		e_to_sys(errno);
27959601Sbostic 		warn("%s: %s", path, strerror(errno));
28059601Sbostic 		return;
28159601Sbostic 	}
282579Sroot 
28359601Sbostic 	notifybiff(biffmsg);
284579Sroot }
285579Sroot 
28650092Sbostic void
28716731Sralph notifybiff(msg)
28816731Sralph 	char *msg;
28916731Sralph {
29016731Sralph 	static struct sockaddr_in addr;
29116731Sralph 	static int f = -1;
29245976Sbostic 	struct hostent *hp;
29345976Sbostic 	struct servent *sp;
29445976Sbostic 	int len;
29516731Sralph 
29645976Sbostic 	if (!addr.sin_family) {
29745976Sbostic 		/* Be silent if biff service not available. */
29845976Sbostic 		if (!(sp = getservbyname("biff", "udp")))
29945976Sbostic 			return;
30045976Sbostic 		if (!(hp = gethostbyname("localhost"))) {
30159601Sbostic 			warn("localhost: %s", strerror(errno));
30245976Sbostic 			return;
30316731Sralph 		}
30445976Sbostic 		addr.sin_family = hp->h_addrtype;
30560097Sbostic 		memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
30645976Sbostic 		addr.sin_port = sp->s_port;
30716731Sralph 	}
30845976Sbostic 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
30959601Sbostic 		warn("socket: %s", strerror(errno));
31045976Sbostic 		return;
31116731Sralph 	}
31245976Sbostic 	len = strlen(msg) + 1;
31346672Sbostic 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
31446672Sbostic 	    != len)
31559601Sbostic 		warn("sendto biff: %s", strerror(errno));
31616731Sralph }
31716731Sralph 
31850092Sbostic void
31945976Sbostic usage()
320579Sroot {
32159601Sbostic 	eval = EX_USAGE;
32259601Sbostic 	err("usage: mail.local [-f from] user ...");
323579Sroot }
324579Sroot 
32550092Sbostic #if __STDC__
32665558Sbostic void
32759601Sbostic err(const char *fmt, ...)
32850092Sbostic #else
32964993Smckusick void
33059601Sbostic err(fmt, va_alist)
33159601Sbostic 	const char *fmt;
33259601Sbostic 	va_dcl
33350092Sbostic #endif
33459601Sbostic {
33559601Sbostic 	va_list ap;
33650092Sbostic 
33759601Sbostic #if __STDC__
33859601Sbostic 	va_start(ap, fmt);
33959601Sbostic #else
34059601Sbostic 	va_start(ap);
34159601Sbostic #endif
34259601Sbostic 	vwarn(fmt, ap);
34359601Sbostic 	va_end(ap);
34459601Sbostic 
34559601Sbostic 	exit(eval);
34659601Sbostic }
34759601Sbostic 
34850092Sbostic void
34950092Sbostic #if __STDC__
35059601Sbostic warn(const char *fmt, ...)
35150092Sbostic #else
35259601Sbostic warn(fmt, va_alist)
35359601Sbostic 	const char *fmt;
35450092Sbostic 	va_dcl
35550092Sbostic #endif
356579Sroot {
35745976Sbostic 	va_list ap;
35859601Sbostic 
35950092Sbostic #if __STDC__
36046554Sbostic 	va_start(ap, fmt);
36150092Sbostic #else
36250092Sbostic 	va_start(ap);
36350092Sbostic #endif
36459601Sbostic 	vwarn(fmt, ap);
36559601Sbostic 	va_end(ap);
36659601Sbostic }
36759601Sbostic 
36859601Sbostic void
36959601Sbostic vwarn(fmt, ap)
37059601Sbostic 	const char *fmt;
37159601Sbostic 	_BSD_VA_LIST_ ap;
37259601Sbostic {
37358400Sbostic 	/*
37459601Sbostic 	 * Log the message to stderr.
37559601Sbostic 	 *
37659601Sbostic 	 * Don't use LOG_PERROR as an openlog() flag to do this,
37759601Sbostic 	 * it's not portable enough.
37858400Sbostic 	 */
37959601Sbostic 	if (eval != EX_USAGE)
38059601Sbostic 		(void)fprintf(stderr, "mail.local: ");
38158396Sbostic 	(void)vfprintf(stderr, fmt, ap);
38258400Sbostic 	(void)fprintf(stderr, "\n");
38359601Sbostic 
38459601Sbostic 	/* Log the message to syslog. */
38545976Sbostic 	vsyslog(LOG_ERR, fmt, ap);
386579Sroot }
38759601Sbostic 
38859601Sbostic /*
38959601Sbostic  * e_to_sys --
39059601Sbostic  *	Guess which errno's are temporary.  Gag me.
39159601Sbostic  */
39259601Sbostic void
39359601Sbostic e_to_sys(num)
39459601Sbostic 	int num;
39559601Sbostic {
39659601Sbostic 	/* Temporary failures override hard errors. */
39759601Sbostic 	if (eval == EX_TEMPFAIL)
39859601Sbostic 		return;
39959601Sbostic 
40059601Sbostic 	switch(num) {		/* Hopefully temporary errors. */
40159601Sbostic #ifdef EAGAIN
40259601Sbostic 	case EAGAIN:		/* Resource temporarily unavailable */
40359601Sbostic #endif
40459601Sbostic #ifdef EDQUOT
40559601Sbostic 	case EDQUOT:		/* Disc quota exceeded */
40659601Sbostic #endif
40759601Sbostic #ifdef EBUSY
40859601Sbostic 	case EBUSY:		/* Device busy */
40959601Sbostic #endif
41059601Sbostic #ifdef EPROCLIM
41159601Sbostic 	case EPROCLIM:		/* Too many processes */
41259601Sbostic #endif
41359601Sbostic #ifdef EUSERS
41459601Sbostic 	case EUSERS:		/* Too many users */
41559601Sbostic #endif
41659601Sbostic #ifdef ECONNABORTED
41759601Sbostic 	case ECONNABORTED:	/* Software caused connection abort */
41859601Sbostic #endif
41959601Sbostic #ifdef ECONNREFUSED
42059601Sbostic 	case ECONNREFUSED:	/* Connection refused */
42159601Sbostic #endif
42259601Sbostic #ifdef ECONNRESET
42359601Sbostic 	case ECONNRESET:	/* Connection reset by peer */
42459601Sbostic #endif
42559601Sbostic #ifdef EDEADLK
42659601Sbostic 	case EDEADLK:		/* Resource deadlock avoided */
42759601Sbostic #endif
42859601Sbostic #ifdef EFBIG
42959601Sbostic 	case EFBIG:		/* File too large */
43059601Sbostic #endif
43159601Sbostic #ifdef EHOSTDOWN
43259601Sbostic 	case EHOSTDOWN:		/* Host is down */
43359601Sbostic #endif
43459601Sbostic #ifdef EHOSTUNREACH
43559601Sbostic 	case EHOSTUNREACH:	/* No route to host */
43659601Sbostic #endif
43759601Sbostic #ifdef EMFILE
43859601Sbostic 	case EMFILE:		/* Too many open files */
43959601Sbostic #endif
44059601Sbostic #ifdef ENETDOWN
44159601Sbostic 	case ENETDOWN:		/* Network is down */
44259601Sbostic #endif
44359601Sbostic #ifdef ENETRESET
44459601Sbostic 	case ENETRESET:		/* Network dropped connection on reset */
44559601Sbostic #endif
44659601Sbostic #ifdef ENETUNREACH
44759601Sbostic 	case ENETUNREACH:	/* Network is unreachable */
44859601Sbostic #endif
44959601Sbostic #ifdef ENFILE
45059601Sbostic 	case ENFILE:		/* Too many open files in system */
45159601Sbostic #endif
45259601Sbostic #ifdef ENOBUFS
45359601Sbostic 	case ENOBUFS:		/* No buffer space available */
45459601Sbostic #endif
45559601Sbostic #ifdef ENOMEM
45659601Sbostic 	case ENOMEM:		/* Cannot allocate memory */
45759601Sbostic #endif
45859601Sbostic #ifdef ENOSPC
45959601Sbostic 	case ENOSPC:		/* No space left on device */
46059601Sbostic #endif
46159601Sbostic #ifdef EROFS
46259601Sbostic 	case EROFS:		/* Read-only file system */
46359601Sbostic #endif
46459601Sbostic #ifdef ESTALE
46559601Sbostic 	case ESTALE:		/* Stale NFS file handle */
46659601Sbostic #endif
46759601Sbostic #ifdef ETIMEDOUT
46859601Sbostic 	case ETIMEDOUT:		/* Connection timed out */
46959601Sbostic #endif
47059601Sbostic #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
47159601Sbostic 	case EWOULDBLOCK:	/* Operation would block. */
47259601Sbostic #endif
47359601Sbostic 		eval = EX_TEMPFAIL;
47459601Sbostic 		break;
47559601Sbostic 	default:
47659601Sbostic 		eval = EX_UNAVAILABLE;
47759601Sbostic 		break;
47859601Sbostic 	}
47959601Sbostic }
480