1 /*-
2  * Copyright (c) 1990, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1990, 1993, 1994\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)mail.local.c	8.12 (Berkeley) 01/16/95";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/stat.h>
20 #include <sys/socket.h>
21 
22 #include <netinet/in.h>
23 
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <netdb.h>
27 #include <pwd.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sysexits.h>
32 #include <syslog.h>
33 #include <time.h>
34 #include <unistd.h>
35 
36 #if __STDC__
37 #include <stdarg.h>
38 #else
39 #include <varargs.h>
40 #endif
41 
42 #ifndef LOCK_EX
43 # include <sys/file.h>
44 #endif
45 
46 #ifdef BSD4_4
47 # include "pathnames.h"
48 #endif
49 
50 #ifndef __P
51 # ifdef __STDC__
52 #  define __P(protos)	protos
53 # else
54 #  define __P(protos)	()
55 #  define const
56 # endif
57 #endif
58 #ifndef __dead
59 # if defined(__GNUC__) && (__GNUC__ < 2 || __GNUC_MINOR__ < 5) && !defined(__STRICT_ANSI__)
60 #  define __dead	__volatile
61 # else
62 #  define __dead
63 # endif
64 #endif
65 
66 #ifndef BSD4_4
67 # define _BSD_VA_LIST_	va_list
68 extern char	*strerror __P((int));
69 #endif
70 
71 #ifndef _PATH_LOCTMP
72 # define _PATH_LOCTMP	"/tmp/local.XXXXXX"
73 #endif
74 #ifndef _PATH_MAILDIR
75 # define _PATH_MAILDIR	"/var/spool/mail"
76 #endif
77 
78 #ifndef S_ISLNK
79 # define S_ISLNK(mode)	(((mode) & _S_IFMT) == S_IFLNK)
80 #endif
81 
82 int eval = EX_OK;			/* sysexits.h error value. */
83 
84 void		deliver __P((int, char *));
85 void		e_to_sys __P((int));
86 __dead void	err __P((const char *, ...));
87 void		notifybiff __P((char *));
88 int		store __P((char *));
89 void		usage __P((void));
90 void		vwarn __P((const char *, _BSD_VA_LIST_));
91 void		warn __P((const char *, ...));
92 
93 int
94 main(argc, argv)
95 	int argc;
96 	char *argv[];
97 {
98 	struct passwd *pw;
99 	int ch, fd;
100 	uid_t uid;
101 	char *from;
102 	extern char *optarg;
103 	extern int optind;
104 
105 #ifdef LOG_MAIL
106 	openlog("mail.local", 0, LOG_MAIL);
107 #else
108 	openlog("mail.local", 0);
109 #endif
110 
111 	from = NULL;
112 	while ((ch = getopt(argc, argv, "df:r:")) != EOF)
113 		switch(ch) {
114 		case 'd':		/* Backward compatible. */
115 			break;
116 		case 'f':
117 		case 'r':		/* Backward compatible. */
118 			if (from != NULL) {
119 				warn("multiple -f options");
120 				usage();
121 			}
122 			from = optarg;
123 			break;
124 		case '?':
125 		default:
126 			usage();
127 		}
128 	argc -= optind;
129 	argv += optind;
130 
131 	if (!*argv)
132 		usage();
133 
134 	/*
135 	 * If from not specified, use the name from getlogin() if the
136 	 * uid matches, otherwise, use the name from the password file
137 	 * corresponding to the uid.
138 	 */
139 	uid = getuid();
140 	if (!from && (!(from = getlogin()) ||
141 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
142 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
143 
144 	/*
145 	 * There is no way to distinguish the error status of one delivery
146 	 * from the rest of the deliveries.  So, if we failed hard on one
147 	 * or more deliveries, but had no failures on any of the others, we
148 	 * return a hard failure.  If we failed temporarily on one or more
149 	 * deliveries, we return a temporary failure regardless of the other
150 	 * failures.  This results in the delivery being reattempted later
151 	 * at the expense of repeated failures and multiple deliveries.
152 	 */
153 	for (fd = store(from); *argv; ++argv)
154 		deliver(fd, *argv);
155 	exit(eval);
156 }
157 
158 int
159 store(from)
160 	char *from;
161 {
162 	FILE *fp;
163 	time_t tval;
164 	int fd, eline;
165 	char line[2048];
166 	char tmpbuf[sizeof _PATH_LOCTMP + 1];
167 
168 	strcpy(tmpbuf, _PATH_LOCTMP);
169 	if ((fd = mkstemp(tmpbuf)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
170 		e_to_sys(errno);
171 		err("unable to open temporary file");
172 	}
173 	(void)unlink(tmpbuf);
174 
175 	(void)time(&tval);
176 	(void)fprintf(fp, "From %s %s", from, ctime(&tval));
177 
178 	line[0] = '\0';
179 	for (eline = 1; fgets(line, sizeof(line), stdin);) {
180 		if (line[0] == '\n')
181 			eline = 1;
182 		else {
183 			if (eline && line[0] == 'F' &&
184 			    !memcmp(line, "From ", 5))
185 				(void)putc('>', fp);
186 			eline = 0;
187 		}
188 		(void)fprintf(fp, "%s", line);
189 		if (ferror(fp)) {
190 			e_to_sys(errno);
191 			err("temporary file write error");
192 		}
193 	}
194 
195 	/* If message not newline terminated, need an extra. */
196 	if (!strchr(line, '\n'))
197 		(void)putc('\n', fp);
198 	/* Output a newline; note, empty messages are allowed. */
199 	(void)putc('\n', fp);
200 
201 	if (fflush(fp) == EOF || ferror(fp)) {
202 		e_to_sys(errno);
203 		err("temporary file write error");
204 	}
205 	return (fd);
206 }
207 
208 void
209 deliver(fd, name)
210 	int fd;
211 	char *name;
212 {
213 	struct stat fsb, sb;
214 	struct passwd *pw;
215 	int mbfd, nr, nw, off;
216 	char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
217 	off_t curoff;
218 
219 	/*
220 	 * Disallow delivery to unknown names -- special mailboxes can be
221 	 * handled in the sendmail aliases file.
222 	 */
223 	if (!(pw = getpwnam(name))) {
224 		if (eval != EX_TEMPFAIL)
225 			eval = EX_UNAVAILABLE;
226 		warn("unknown name: %s", name);
227 		return;
228 	}
229 
230 	(void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
231 
232 	/*
233 	 * If the mailbox is linked or a symlink, fail.  There's an obvious
234 	 * race here, that the file was replaced with a symbolic link after
235 	 * the lstat returned, but before the open.  We attempt to detect
236 	 * this by comparing the original stat information and information
237 	 * returned by an fstat of the file descriptor returned by the open.
238 	 *
239 	 * NB: this is a symptom of a larger problem, that the mail spooling
240 	 * directory is writeable by the wrong users.  If that directory is
241 	 * writeable, system security is compromised for other reasons, and
242 	 * it cannot be fixed here.
243 	 *
244 	 * If we created the mailbox, set the owner/group.  If that fails,
245 	 * just return.  Another process may have already opened it, so we
246 	 * can't unlink it.  Historically, binmail set the owner/group at
247 	 * each mail delivery.  We no longer do this, assuming that if the
248 	 * ownership or permissions were changed there was a reason.
249 	 *
250 	 * XXX
251 	 * open(2) should support flock'ing the file.
252 	 */
253 tryagain:
254 	lockmbox(path);
255 	if (lstat(path, &sb)) {
256 		mbfd = open(path,
257 		    O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
258 		if (mbfd == -1) {
259 			if (errno == EEXIST)
260 				goto tryagain;
261 		} else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
262 			e_to_sys(errno);
263 			warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
264 			unlockmbox();
265 			return;
266 		}
267 	} else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
268 		e_to_sys(errno);
269 		warn("%s: linked file", path);
270 		unlockmbox();
271 		return;
272 	} else if (sb.st_uid != pw->pw_uid) {
273 		warn("%s: wrong ownership (%d)", path, sb.st_uid);
274 		unlockmbox();
275 		return;
276 	} else {
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) || sb.st_uid != fsb.st_uid) {
282 			warn("%s: file changed after open", path);
283 			(void)close(mbfd);
284 			unlockmbox();
285 			return;
286 		}
287 	}
288 
289 	if (mbfd == -1) {
290 		e_to_sys(errno);
291 		warn("%s: %s", path, strerror(errno));
292 		unlockmbox();
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 		unlockmbox();
301 		goto err1;
302 	}
303 
304 	/* Get the starting offset of the new message for biff. */
305 	curoff = lseek(mbfd, (off_t)0, SEEK_END);
306 	(void)snprintf(biffmsg, sizeof(biffmsg),
307 		sizeof curoff > sizeof(long) ? "%s@%qd\n" : "%s@%ld\n",
308 		name, curoff);
309 
310 	/* Copy the message into the file. */
311 	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
312 		e_to_sys(errno);
313 		warn("temporary file: %s", strerror(errno));
314 		goto err1;
315 	}
316 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
317 		for (off = 0; off < nr; nr -= nw, off += nw)
318 			if ((nw = write(mbfd, buf + off, nr)) < 0) {
319 				e_to_sys(errno);
320 				warn("%s: %s", path, strerror(errno));
321 				goto err2;;
322 			}
323 	if (nr < 0) {
324 		e_to_sys(errno);
325 		warn("temporary file: %s", strerror(errno));
326 		goto err2;;
327 	}
328 
329 	/* Flush to disk, don't wait for update. */
330 	if (fsync(mbfd)) {
331 		e_to_sys(errno);
332 		warn("%s: %s", path, strerror(errno));
333 err2:		(void)ftruncate(mbfd, curoff);
334 err1:		(void)close(mbfd);
335 		unlockmbox();
336 		return;
337 	}
338 
339 	/* Close and check -- NFS doesn't write until the close. */
340 	if (close(mbfd)) {
341 		e_to_sys(errno);
342 		warn("%s: %s", path, strerror(errno));
343 		unlockmbox();
344 		return;
345 	}
346 
347 	unlockmbox();
348 	notifybiff(biffmsg);
349 }
350 
351 /*
352  * user.lock files are necessary for compatibility with other
353  * systems, e.g., when the mail spool file is NFS exported.
354  * Alas, mailbox locking is more than just a local matter.
355  * EPA 11/94.
356  */
357 
358 char	lockname[50];
359 int	locked = 0;
360 
361 lockmbox(path)
362 	char *path;
363 {
364 	int statfailed = 0;
365 
366 	if (locked)
367 		return;
368 	sprintf(lockname, "%s.lock", path);
369 	for (;; sleep(5)) {
370 		int fd;
371 		struct stat st;
372 		time_t now;
373 
374 		fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0);
375 		if (fd >= 0) {
376 			locked = 1;
377 			close(fd);
378 			return;
379 		}
380 		if (stat(lockname, &st) < 0) {
381 			if (statfailed++ > 5)
382 				return;
383 			continue;
384 		}
385 		statfailed = 0;
386 		time(&now);
387 		if (now < st.st_ctime + 300)
388 			continue;
389 		unlink(lockname);
390 	}
391 }
392 
393 unlockmbox()
394 {
395 	if (!locked)
396 		return;
397 	unlink(lockname);
398 	locked = 0;
399 }
400 
401 void
402 notifybiff(msg)
403 	char *msg;
404 {
405 	static struct sockaddr_in addr;
406 	static int f = -1;
407 	struct hostent *hp;
408 	struct servent *sp;
409 	int len;
410 
411 	if (!addr.sin_family) {
412 		/* Be silent if biff service not available. */
413 		if (!(sp = getservbyname("biff", "udp")))
414 			return;
415 		if (!(hp = gethostbyname("localhost"))) {
416 			warn("localhost: %s", strerror(errno));
417 			return;
418 		}
419 		addr.sin_family = hp->h_addrtype;
420 		memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
421 		addr.sin_port = sp->s_port;
422 	}
423 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
424 		warn("socket: %s", strerror(errno));
425 		return;
426 	}
427 	len = strlen(msg) + 1;
428 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
429 	    != len)
430 		warn("sendto biff: %s", strerror(errno));
431 }
432 
433 void
434 usage()
435 {
436 	eval = EX_USAGE;
437 	err("usage: mail.local [-f from] user ...");
438 }
439 
440 #if __STDC__
441 void
442 err(const char *fmt, ...)
443 #else
444 void
445 err(fmt, va_alist)
446 	const char *fmt;
447 	va_dcl
448 #endif
449 {
450 	va_list ap;
451 
452 #if __STDC__
453 	va_start(ap, fmt);
454 #else
455 	va_start(ap);
456 #endif
457 	vwarn(fmt, ap);
458 	va_end(ap);
459 
460 	exit(eval);
461 }
462 
463 void
464 #if __STDC__
465 warn(const char *fmt, ...)
466 #else
467 warn(fmt, va_alist)
468 	const char *fmt;
469 	va_dcl
470 #endif
471 {
472 	va_list ap;
473 
474 #if __STDC__
475 	va_start(ap, fmt);
476 #else
477 	va_start(ap);
478 #endif
479 	vwarn(fmt, ap);
480 	va_end(ap);
481 }
482 
483 void
484 vwarn(fmt, ap)
485 	const char *fmt;
486 	_BSD_VA_LIST_ ap;
487 {
488 	/*
489 	 * Log the message to stderr.
490 	 *
491 	 * Don't use LOG_PERROR as an openlog() flag to do this,
492 	 * it's not portable enough.
493 	 */
494 	if (eval != EX_USAGE)
495 		(void)fprintf(stderr, "mail.local: ");
496 	(void)vfprintf(stderr, fmt, ap);
497 	(void)fprintf(stderr, "\n");
498 
499 #ifndef ultrix
500 	/* Log the message to syslog. */
501 	vsyslog(LOG_ERR, fmt, ap);
502 #else
503 	{
504 		char fmtbuf[10240];
505 
506 		(void) sprintf(fmtbuf, fmt, ap);
507 		syslog(LOG_ERR, "%s", fmtbuf);
508 	}
509 #endif
510 }
511 
512 /*
513  * e_to_sys --
514  *	Guess which errno's are temporary.  Gag me.
515  */
516 void
517 e_to_sys(num)
518 	int num;
519 {
520 	/* Temporary failures override hard errors. */
521 	if (eval == EX_TEMPFAIL)
522 		return;
523 
524 	switch(num) {		/* Hopefully temporary errors. */
525 #ifdef EAGAIN
526 	case EAGAIN:		/* Resource temporarily unavailable */
527 #endif
528 #ifdef EDQUOT
529 	case EDQUOT:		/* Disc quota exceeded */
530 #endif
531 #ifdef EBUSY
532 	case EBUSY:		/* Device busy */
533 #endif
534 #ifdef EPROCLIM
535 	case EPROCLIM:		/* Too many processes */
536 #endif
537 #ifdef EUSERS
538 	case EUSERS:		/* Too many users */
539 #endif
540 #ifdef ECONNABORTED
541 	case ECONNABORTED:	/* Software caused connection abort */
542 #endif
543 #ifdef ECONNREFUSED
544 	case ECONNREFUSED:	/* Connection refused */
545 #endif
546 #ifdef ECONNRESET
547 	case ECONNRESET:	/* Connection reset by peer */
548 #endif
549 #ifdef EDEADLK
550 	case EDEADLK:		/* Resource deadlock avoided */
551 #endif
552 #ifdef EFBIG
553 	case EFBIG:		/* File too large */
554 #endif
555 #ifdef EHOSTDOWN
556 	case EHOSTDOWN:		/* Host is down */
557 #endif
558 #ifdef EHOSTUNREACH
559 	case EHOSTUNREACH:	/* No route to host */
560 #endif
561 #ifdef EMFILE
562 	case EMFILE:		/* Too many open files */
563 #endif
564 #ifdef ENETDOWN
565 	case ENETDOWN:		/* Network is down */
566 #endif
567 #ifdef ENETRESET
568 	case ENETRESET:		/* Network dropped connection on reset */
569 #endif
570 #ifdef ENETUNREACH
571 	case ENETUNREACH:	/* Network is unreachable */
572 #endif
573 #ifdef ENFILE
574 	case ENFILE:		/* Too many open files in system */
575 #endif
576 #ifdef ENOBUFS
577 	case ENOBUFS:		/* No buffer space available */
578 #endif
579 #ifdef ENOMEM
580 	case ENOMEM:		/* Cannot allocate memory */
581 #endif
582 #ifdef ENOSPC
583 	case ENOSPC:		/* No space left on device */
584 #endif
585 #ifdef EROFS
586 	case EROFS:		/* Read-only file system */
587 #endif
588 #ifdef ESTALE
589 	case ESTALE:		/* Stale NFS file handle */
590 #endif
591 #ifdef ETIMEDOUT
592 	case ETIMEDOUT:		/* Connection timed out */
593 #endif
594 #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN && EWOULDBLOCK != EDEADLK
595 	case EWOULDBLOCK:	/* Operation would block. */
596 #endif
597 		eval = EX_TEMPFAIL;
598 		break;
599 	default:
600 		eval = EX_UNAVAILABLE;
601 		break;
602 	}
603 }
604 
605 #ifndef BSD4_4
606 
607 char *
608 strerror(eno)
609 	int eno;
610 {
611 	extern int sys_nerr;
612 	extern char *sys_errlist[];
613 	static char ebuf[60];
614 
615 	if (eno >= 0 && eno <= sys_nerr)
616 		return sys_errlist[eno];
617 	(void) sprintf(ebuf, "Error %d", eno);
618 	return ebuf;
619 }
620 
621 #if __STDC__
622 snprintf(char *buf, int bufsiz, const char *fmt, ...)
623 #else
624 snprintf(buf, bufsiz, fmt, va_alist)
625 	char *buf;
626 	int bufsiz;
627 	const char *fmt;
628 	va_dcl
629 #endif
630 {
631 	va_list ap;
632 
633 #if __STDC__
634 	va_start(ap, fmt);
635 #else
636 	va_start(ap);
637 #endif
638 	vsprintf(buf, fmt, ap);
639 	va_end(ap);
640 }
641 
642 #endif
643 
644 #ifdef ultrix
645 
646 int
647 mkstemp(template)
648 	char *template;
649 {
650 	int fd;
651 
652 	return open(mktemp(template), O_RDWR|O_CREAT|O_EXCL, 0600);
653 }
654 
655 #endif
656