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*59601Sbostic static char sccsid[] = "@(#)mail.local.c	5.12 (Berkeley) 04/30/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>
31*59601Sbostic #include <sysexits.h>
3257648Sbostic #include <syslog.h>
3357648Sbostic #include <time.h>
3457648Sbostic #include <unistd.h>
3557648Sbostic 
36*59601Sbostic #if __STDC__
37*59601Sbostic #include <stdarg.h>
38*59601Sbostic #else
39*59601Sbostic #include <varargs.h>
40*59601Sbostic #endif
41*59601Sbostic 
4237465Sbostic #include "pathnames.h"
43579Sroot 
44*59601Sbostic int eval = EX_OK;			/* sysexits.h error value. */
45579Sroot 
46*59601Sbostic void		deliver __P((int, char *));
47*59601Sbostic void		e_to_sys __P((int));
48*59601Sbostic __dead void	err __P((const char *, ...));
49*59601Sbostic void		notifybiff __P((char *));
50*59601Sbostic int		store __P((char *));
51*59601Sbostic void		usage __P((void));
52*59601Sbostic void		vwarn __P((const char *, _BSD_VA_LIST_));
53*59601Sbostic void		warn __P((const char *, ...));
5450092Sbostic 
5557648Sbostic int
56579Sroot main(argc, argv)
5745976Sbostic 	int argc;
58*59601Sbostic 	char *argv[];
59579Sroot {
6045976Sbostic 	struct passwd *pw;
61*59601Sbostic 	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) {
70*59601Sbostic 		case 'd':		/* Backward compatible. */
7116731Sralph 			break;
7216731Sralph 		case 'f':
73*59601Sbostic 		case 'r':		/* Backward compatible. */
74*59601Sbostic 			if (from != NULL) {
75*59601Sbostic 				warn("multiple -f options");
76*59601Sbostic 				usage();
77*59601Sbostic 			}
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 
100*59601Sbostic 	/*
101*59601Sbostic 	 * There is no way to distinguish the error status of one delivery
102*59601Sbostic 	 * from the rest of the deliveries.  So, if we failed hard on one
103*59601Sbostic 	 * or more deliveries, but had no failures on any of the others, we
104*59601Sbostic 	 * return a hard failure.  If we failed temporarily on one or more
105*59601Sbostic 	 * deliveries, we return a temporary failure regardless of the other
106*59601Sbostic 	 * failures.  This results in the delivery being reattempted later
107*59601Sbostic 	 * at the expense of repeated failures and multiple deliveries.
108*59601Sbostic 	 */
109*59601Sbostic 	for (fd = store(from); *argv; ++argv)
110*59601Sbostic 		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);
124*59601Sbostic 	if ((fd = mkstemp(tn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
125*59601Sbostic 		e_to_sys(errno);
126*59601Sbostic 		err("unable to open temporary file");
127*59601Sbostic 	}
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 {
13945976Sbostic 			if (eline && line[0] == 'F' && !bcmp(line, "From ", 5))
14045976Sbostic 				(void)putc('>', fp);
14145976Sbostic 			eline = 0;
14245976Sbostic 		}
14345976Sbostic 		(void)fprintf(fp, "%s", line);
144*59601Sbostic 		if (ferror(fp)) {
145*59601Sbostic 			e_to_sys(errno);
146*59601Sbostic 			err("temporary file write error");
147*59601Sbostic 		}
148579Sroot 	}
149579Sroot 
15045976Sbostic 	/* If message not newline terminated, need an extra. */
151*59601Sbostic 	if (!strchr(line, '\n'))
15245976Sbostic 		(void)putc('\n', fp);
15345976Sbostic 	/* Output a newline; note, empty messages are allowed. */
15445976Sbostic 	(void)putc('\n', fp);
15511893Seric 
156*59601Sbostic 	if (fflush(fp) == EOF || ferror(fp)) {
157*59601Sbostic 		e_to_sys(errno);
158*59601Sbostic 		err("temporary file write error");
159*59601Sbostic 	}
160*59601Sbostic 	return (fd);
161579Sroot }
162579Sroot 
163*59601Sbostic void
16445976Sbostic deliver(fd, name)
16545976Sbostic 	int fd;
16645976Sbostic 	char *name;
167579Sroot {
16845976Sbostic 	struct stat sb;
16945976Sbostic 	struct passwd *pw;
170*59601Sbostic 	int mbfd, nr, nw, off;
17145976Sbostic 	char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
17254203Sbostic 	off_t curoff;
173579Sroot 
17445976Sbostic 	/*
17545976Sbostic 	 * Disallow delivery to unknown names -- special mailboxes can be
17645976Sbostic 	 * handled in the sendmail aliases file.
17745976Sbostic 	 */
17845976Sbostic 	if (!(pw = getpwnam(name))) {
179*59601Sbostic 		if (eval != EX_TEMPFAIL)
180*59601Sbostic 			eval = EX_UNAVAILABLE;
181*59601Sbostic 		warn("unknown name: %s", name);
182*59601Sbostic 		return;
18345976Sbostic 	}
184579Sroot 
18557648Sbostic 	(void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
186579Sroot 
187579Sroot 	/*
188*59601Sbostic 	 * If the mailbox is a linked or a symlink, fail.
189*59601Sbostic 	 *
190*59601Sbostic 	 * If we created the mailbox, set the owner/group.  If that fails,
191*59601Sbostic 	 * just return.  Another process may have already opened it, so we
192*59601Sbostic 	 * can't unlink it.  Historically, binmail set the owner/group at
193*59601Sbostic 	 * each mail delivery.  We no longer do this, assuming that if the
194*59601Sbostic 	 * ownership or permissions were changed there was a reason.
195*59601Sbostic 	 *
196*59601Sbostic 	 * XXX
197*59601Sbostic 	 * open(2) should support flock'ing the file.
198579Sroot 	 */
199*59601Sbostic 	if (lstat(path, &sb)) {
200*59601Sbostic 		if ((mbfd = open(path,
201*59601Sbostic 		    O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR)) < 0)
202*59601Sbostic 			mbfd = open(path, O_APPEND|O_WRONLY, 0);
203*59601Sbostic 		else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
204*59601Sbostic 			e_to_sys(errno);
205*59601Sbostic 			warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
206*59601Sbostic 			return;
207*59601Sbostic 		}
208*59601Sbostic 	} else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
209*59601Sbostic 		e_to_sys(errno);
210*59601Sbostic 		warn("%s: linked file", path);
211*59601Sbostic 		return;
212*59601Sbostic 	} else
213*59601Sbostic 		mbfd = open(path, O_APPEND|O_WRONLY, 0);
214*59601Sbostic 
215*59601Sbostic 	if (mbfd == -1) {
216*59601Sbostic 		e_to_sys(errno);
217*59601Sbostic 		warn("%s: %s", path, strerror(errno));
218*59601Sbostic 		return;
21945976Sbostic 	}
220579Sroot 
221*59601Sbostic 	/* Wait until we can get a lock on the file. */
22245976Sbostic 	if (flock(mbfd, LOCK_EX)) {
223*59601Sbostic 		e_to_sys(errno);
224*59601Sbostic 		warn("%s: %s", path, strerror(errno));
225*59601Sbostic 		goto err1;
22645976Sbostic 	}
227579Sroot 
228*59601Sbostic 	/* Get the starting offset of the new message for biff. */
22954203Sbostic 	curoff = lseek(mbfd, (off_t)0, SEEK_END);
23057648Sbostic 	(void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n", name, curoff);
231*59601Sbostic 
232*59601Sbostic 	/* Copy the message into the file. */
23354203Sbostic 	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
234*59601Sbostic 		e_to_sys(errno);
235*59601Sbostic 		warn("temporary file: %s", strerror(errno));
236*59601Sbostic 		goto err1;
23745976Sbostic 	}
23845976Sbostic 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
23945976Sbostic 		for (off = 0; off < nr; nr -= nw, off += nw)
24045976Sbostic 			if ((nw = write(mbfd, buf + off, nr)) < 0) {
241*59601Sbostic 				e_to_sys(errno);
242*59601Sbostic 				warn("%s: %s", path, strerror(errno));
243*59601Sbostic 				goto err2;;
24445976Sbostic 			}
24545976Sbostic 	if (nr < 0) {
246*59601Sbostic 		e_to_sys(errno);
247*59601Sbostic 		warn("temporary file: %s", strerror(errno));
248*59601Sbostic 		goto err2;;
249579Sroot 	}
250579Sroot 
251*59601Sbostic 	/* Flush to disk, don't wait for update. */
252*59601Sbostic 	if (fsync(mbfd)) {
253*59601Sbostic 		e_to_sys(errno);
254*59601Sbostic 		warn("%s: %s", path, strerror(errno));
255*59601Sbostic err2:		(void)ftruncate(mbfd, curoff);
256*59601Sbostic err1:		(void)close(mbfd);
257*59601Sbostic 		return;
258*59601Sbostic 	}
259*59601Sbostic 
260*59601Sbostic 	/* Close and check -- NFS doesn't write until the close. */
261*59601Sbostic 	if (close(mbfd)) {
262*59601Sbostic 		e_to_sys(errno);
263*59601Sbostic 		warn("%s: %s", path, strerror(errno));
264*59601Sbostic 		return;
265*59601Sbostic 	}
266579Sroot 
267*59601Sbostic 	notifybiff(biffmsg);
268579Sroot }
269579Sroot 
27050092Sbostic void
27116731Sralph notifybiff(msg)
27216731Sralph 	char *msg;
27316731Sralph {
27416731Sralph 	static struct sockaddr_in addr;
27516731Sralph 	static int f = -1;
27645976Sbostic 	struct hostent *hp;
27745976Sbostic 	struct servent *sp;
27845976Sbostic 	int len;
27916731Sralph 
28045976Sbostic 	if (!addr.sin_family) {
28145976Sbostic 		/* Be silent if biff service not available. */
28245976Sbostic 		if (!(sp = getservbyname("biff", "udp")))
28345976Sbostic 			return;
28445976Sbostic 		if (!(hp = gethostbyname("localhost"))) {
285*59601Sbostic 			warn("localhost: %s", strerror(errno));
28645976Sbostic 			return;
28716731Sralph 		}
28845976Sbostic 		addr.sin_family = hp->h_addrtype;
28945976Sbostic 		bcopy(hp->h_addr, &addr.sin_addr, hp->h_length);
29045976Sbostic 		addr.sin_port = sp->s_port;
29116731Sralph 	}
29245976Sbostic 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
293*59601Sbostic 		warn("socket: %s", strerror(errno));
29445976Sbostic 		return;
29516731Sralph 	}
29645976Sbostic 	len = strlen(msg) + 1;
29746672Sbostic 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
29846672Sbostic 	    != len)
299*59601Sbostic 		warn("sendto biff: %s", strerror(errno));
30016731Sralph }
30116731Sralph 
30250092Sbostic void
30345976Sbostic usage()
304579Sroot {
305*59601Sbostic 	eval = EX_USAGE;
306*59601Sbostic 	err("usage: mail.local [-f from] user ...");
307579Sroot }
308579Sroot 
309*59601Sbostic void
31050092Sbostic #if __STDC__
311*59601Sbostic err(const char *fmt, ...)
31250092Sbostic #else
313*59601Sbostic err(fmt, va_alist)
314*59601Sbostic 	const char *fmt;
315*59601Sbostic 	va_dcl
31650092Sbostic #endif
317*59601Sbostic {
318*59601Sbostic 	va_list ap;
31950092Sbostic 
320*59601Sbostic #if __STDC__
321*59601Sbostic 	va_start(ap, fmt);
322*59601Sbostic #else
323*59601Sbostic 	va_start(ap);
324*59601Sbostic #endif
325*59601Sbostic 	vwarn(fmt, ap);
326*59601Sbostic 	va_end(ap);
327*59601Sbostic 
328*59601Sbostic 	exit(eval);
329*59601Sbostic }
330*59601Sbostic 
33150092Sbostic void
33250092Sbostic #if __STDC__
333*59601Sbostic warn(const char *fmt, ...)
33450092Sbostic #else
335*59601Sbostic warn(fmt, va_alist)
336*59601Sbostic 	const char *fmt;
33750092Sbostic 	va_dcl
33850092Sbostic #endif
339579Sroot {
34045976Sbostic 	va_list ap;
341*59601Sbostic 
34250092Sbostic #if __STDC__
34346554Sbostic 	va_start(ap, fmt);
34450092Sbostic #else
34550092Sbostic 	va_start(ap);
34650092Sbostic #endif
347*59601Sbostic 	vwarn(fmt, ap);
348*59601Sbostic 	va_end(ap);
349*59601Sbostic }
350*59601Sbostic 
351*59601Sbostic void
352*59601Sbostic vwarn(fmt, ap)
353*59601Sbostic 	const char *fmt;
354*59601Sbostic 	_BSD_VA_LIST_ ap;
355*59601Sbostic {
35658400Sbostic 	/*
357*59601Sbostic 	 * Log the message to stderr.
358*59601Sbostic 	 *
359*59601Sbostic 	 * Don't use LOG_PERROR as an openlog() flag to do this,
360*59601Sbostic 	 * it's not portable enough.
36158400Sbostic 	 */
362*59601Sbostic 	if (eval != EX_USAGE)
363*59601Sbostic 		(void)fprintf(stderr, "mail.local: ");
36458396Sbostic 	(void)vfprintf(stderr, fmt, ap);
36558400Sbostic 	(void)fprintf(stderr, "\n");
366*59601Sbostic 
367*59601Sbostic 	/* Log the message to syslog. */
36845976Sbostic 	vsyslog(LOG_ERR, fmt, ap);
369579Sroot }
370*59601Sbostic 
371*59601Sbostic /*
372*59601Sbostic  * e_to_sys --
373*59601Sbostic  *	Guess which errno's are temporary.  Gag me.
374*59601Sbostic  */
375*59601Sbostic void
376*59601Sbostic e_to_sys(num)
377*59601Sbostic 	int num;
378*59601Sbostic {
379*59601Sbostic 	/* Temporary failures override hard errors. */
380*59601Sbostic 	if (eval == EX_TEMPFAIL)
381*59601Sbostic 		return;
382*59601Sbostic 
383*59601Sbostic 	switch(num) {		/* Hopefully temporary errors. */
384*59601Sbostic #ifdef EAGAIN
385*59601Sbostic 	case EAGAIN:		/* Resource temporarily unavailable */
386*59601Sbostic #endif
387*59601Sbostic #ifdef EDQUOT
388*59601Sbostic 	case EDQUOT:		/* Disc quota exceeded */
389*59601Sbostic #endif
390*59601Sbostic #ifdef EBUSY
391*59601Sbostic 	case EBUSY:		/* Device busy */
392*59601Sbostic #endif
393*59601Sbostic #ifdef EPROCLIM
394*59601Sbostic 	case EPROCLIM:		/* Too many processes */
395*59601Sbostic #endif
396*59601Sbostic #ifdef EUSERS
397*59601Sbostic 	case EUSERS:		/* Too many users */
398*59601Sbostic #endif
399*59601Sbostic #ifdef ECONNABORTED
400*59601Sbostic 	case ECONNABORTED:	/* Software caused connection abort */
401*59601Sbostic #endif
402*59601Sbostic #ifdef ECONNREFUSED
403*59601Sbostic 	case ECONNREFUSED:	/* Connection refused */
404*59601Sbostic #endif
405*59601Sbostic #ifdef ECONNRESET
406*59601Sbostic 	case ECONNRESET:	/* Connection reset by peer */
407*59601Sbostic #endif
408*59601Sbostic #ifdef EDEADLK
409*59601Sbostic 	case EDEADLK:		/* Resource deadlock avoided */
410*59601Sbostic #endif
411*59601Sbostic #ifdef EFBIG
412*59601Sbostic 	case EFBIG:		/* File too large */
413*59601Sbostic #endif
414*59601Sbostic #ifdef EHOSTDOWN
415*59601Sbostic 	case EHOSTDOWN:		/* Host is down */
416*59601Sbostic #endif
417*59601Sbostic #ifdef EHOSTUNREACH
418*59601Sbostic 	case EHOSTUNREACH:	/* No route to host */
419*59601Sbostic #endif
420*59601Sbostic #ifdef EMFILE
421*59601Sbostic 	case EMFILE:		/* Too many open files */
422*59601Sbostic #endif
423*59601Sbostic #ifdef ENETDOWN
424*59601Sbostic 	case ENETDOWN:		/* Network is down */
425*59601Sbostic #endif
426*59601Sbostic #ifdef ENETRESET
427*59601Sbostic 	case ENETRESET:		/* Network dropped connection on reset */
428*59601Sbostic #endif
429*59601Sbostic #ifdef ENETUNREACH
430*59601Sbostic 	case ENETUNREACH:	/* Network is unreachable */
431*59601Sbostic #endif
432*59601Sbostic #ifdef ENFILE
433*59601Sbostic 	case ENFILE:		/* Too many open files in system */
434*59601Sbostic #endif
435*59601Sbostic #ifdef ENOBUFS
436*59601Sbostic 	case ENOBUFS:		/* No buffer space available */
437*59601Sbostic #endif
438*59601Sbostic #ifdef ENOMEM
439*59601Sbostic 	case ENOMEM:		/* Cannot allocate memory */
440*59601Sbostic #endif
441*59601Sbostic #ifdef ENOSPC
442*59601Sbostic 	case ENOSPC:		/* No space left on device */
443*59601Sbostic #endif
444*59601Sbostic #ifdef EROFS
445*59601Sbostic 	case EROFS:		/* Read-only file system */
446*59601Sbostic #endif
447*59601Sbostic #ifdef ESTALE
448*59601Sbostic 	case ESTALE:		/* Stale NFS file handle */
449*59601Sbostic #endif
450*59601Sbostic #ifdef ETIMEDOUT
451*59601Sbostic 	case ETIMEDOUT:		/* Connection timed out */
452*59601Sbostic #endif
453*59601Sbostic #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
454*59601Sbostic 	case EWOULDBLOCK:	/* Operation would block. */
455*59601Sbostic #endif
456*59601Sbostic 		eval = EX_TEMPFAIL;
457*59601Sbostic 		break;
458*59601Sbostic 	default:
459*59601Sbostic 		eval = EX_UNAVAILABLE;
460*59601Sbostic 		break;
461*59601Sbostic 	}
462*59601Sbostic }
463