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.6 (Berkeley) 04/08/94";
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 #include "pathnames.h"
43 
44 int eval = EX_OK;			/* sysexits.h error value. */
45 
46 void		deliver __P((int, char *));
47 void		e_to_sys __P((int));
48 __dead void	err __P((const char *, ...));
49 void		notifybiff __P((char *));
50 int		store __P((char *));
51 void		usage __P((void));
52 void		vwarn __P((const char *, _BSD_VA_LIST_));
53 void		warn __P((const char *, ...));
54 
55 int
56 main(argc, argv)
57 	int argc;
58 	char *argv[];
59 {
60 	struct passwd *pw;
61 	int ch, fd;
62 	uid_t uid;
63 	char *from;
64 
65 	openlog("mail.local", 0, LOG_MAIL);
66 
67 	from = NULL;
68 	while ((ch = getopt(argc, argv, "df:r:")) != EOF)
69 		switch(ch) {
70 		case 'd':		/* Backward compatible. */
71 			break;
72 		case 'f':
73 		case 'r':		/* Backward compatible. */
74 			if (from != NULL) {
75 				warn("multiple -f options");
76 				usage();
77 			}
78 			from = optarg;
79 			break;
80 		case '?':
81 		default:
82 			usage();
83 		}
84 	argc -= optind;
85 	argv += optind;
86 
87 	if (!*argv)
88 		usage();
89 
90 	/*
91 	 * If from not specified, use the name from getlogin() if the
92 	 * uid matches, otherwise, use the name from the password file
93 	 * corresponding to the uid.
94 	 */
95 	uid = getuid();
96 	if (!from && (!(from = getlogin()) ||
97 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
98 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
99 
100 	/*
101 	 * There is no way to distinguish the error status of one delivery
102 	 * from the rest of the deliveries.  So, if we failed hard on one
103 	 * or more deliveries, but had no failures on any of the others, we
104 	 * return a hard failure.  If we failed temporarily on one or more
105 	 * deliveries, we return a temporary failure regardless of the other
106 	 * failures.  This results in the delivery being reattempted later
107 	 * at the expense of repeated failures and multiple deliveries.
108 	 */
109 	for (fd = store(from); *argv; ++argv)
110 		deliver(fd, *argv);
111 	exit(eval);
112 }
113 
114 int
115 store(from)
116 	char *from;
117 {
118 	FILE *fp;
119 	time_t tval;
120 	int fd, eline;
121 	char *tn, line[2048];
122 
123 	tn = strdup(_PATH_LOCTMP);
124 	if ((fd = mkstemp(tn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
125 		e_to_sys(errno);
126 		err("unable to open temporary file");
127 	}
128 	(void)unlink(tn);
129 	free(tn);
130 
131 	(void)time(&tval);
132 	(void)fprintf(fp, "From %s %s", from, ctime(&tval));
133 
134 	line[0] = '\0';
135 	for (eline = 1; fgets(line, sizeof(line), stdin);) {
136 		if (line[0] == '\n')
137 			eline = 1;
138 		else {
139 			if (eline && line[0] == 'F' &&
140 			    !memcmp(line, "From ", 5))
141 				(void)putc('>', fp);
142 			eline = 0;
143 		}
144 		(void)fprintf(fp, "%s", line);
145 		if (ferror(fp)) {
146 			e_to_sys(errno);
147 			err("temporary file write error");
148 		}
149 	}
150 
151 	/* If message not newline terminated, need an extra. */
152 	if (!strchr(line, '\n'))
153 		(void)putc('\n', fp);
154 	/* Output a newline; note, empty messages are allowed. */
155 	(void)putc('\n', fp);
156 
157 	if (fflush(fp) == EOF || ferror(fp)) {
158 		e_to_sys(errno);
159 		err("temporary file write error");
160 	}
161 	return (fd);
162 }
163 
164 void
165 deliver(fd, name)
166 	int fd;
167 	char *name;
168 {
169 	struct stat fsb, sb;
170 	struct passwd *pw;
171 	int mbfd, nr, nw, off;
172 	char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
173 	off_t curoff;
174 
175 	/*
176 	 * Disallow delivery to unknown names -- special mailboxes can be
177 	 * handled in the sendmail aliases file.
178 	 */
179 	if (!(pw = getpwnam(name))) {
180 		if (eval != EX_TEMPFAIL)
181 			eval = EX_UNAVAILABLE;
182 		warn("unknown name: %s", name);
183 		return;
184 	}
185 
186 	(void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
187 
188 	/*
189 	 * If the mailbox is linked or a symlink, fail.  There's an obvious
190 	 * race here, that the file was replaced with a symbolic link after
191 	 * the lstat returned, but before the open.  We attempt to detect
192 	 * this by comparing the original stat information and information
193 	 * returned by an fstat of the file descriptor returned by the open.
194 	 *
195 	 * NB: this is a symptom of a larger problem, that the mail spooling
196 	 * directory is writeable by the wrong users.  If that directory is
197 	 * writeable, system security is compromised for other reasons, and
198 	 * it cannot be fixed here.
199 	 *
200 	 * If we created the mailbox, set the owner/group.  If that fails,
201 	 * just return.  Another process may have already opened it, so we
202 	 * can't unlink it.  Historically, binmail set the owner/group at
203 	 * each mail delivery.  We no longer do this, assuming that if the
204 	 * ownership or permissions were changed there was a reason.
205 	 *
206 	 * XXX
207 	 * open(2) should support flock'ing the file.
208 	 */
209 tryagain:
210 	if (lstat(path, &sb)) {
211 		mbfd = open(path,
212 		    O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
213 		if (mbfd == -1) {
214 			if (errno == EEXIST)
215 				goto tryagain;
216 		} else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
217 			e_to_sys(errno);
218 			warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
219 			return;
220 		}
221 	} else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
222 		e_to_sys(errno);
223 		warn("%s: linked file", path);
224 		return;
225 	} else {
226 		mbfd = open(path, O_APPEND|O_WRONLY, 0);
227 		if (mbfd != -1 &&
228 		    (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
229 		    S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
230 		    sb.st_ino != fsb.st_ino)) {
231 			warn("%s: file changed after open", path);
232 			(void)close(mbfd);
233 			return;
234 		}
235 	}
236 
237 	if (mbfd == -1) {
238 		e_to_sys(errno);
239 		warn("%s: %s", path, strerror(errno));
240 		return;
241 	}
242 
243 	/* Wait until we can get a lock on the file. */
244 	if (flock(mbfd, LOCK_EX)) {
245 		e_to_sys(errno);
246 		warn("%s: %s", path, strerror(errno));
247 		goto err1;
248 	}
249 
250 	/* Get the starting offset of the new message for biff. */
251 	curoff = lseek(mbfd, (off_t)0, SEEK_END);
252 	(void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n", name, curoff);
253 
254 	/* Copy the message into the file. */
255 	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
256 		e_to_sys(errno);
257 		warn("temporary file: %s", strerror(errno));
258 		goto err1;
259 	}
260 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
261 		for (off = 0; off < nr; nr -= nw, off += nw)
262 			if ((nw = write(mbfd, buf + off, nr)) < 0) {
263 				e_to_sys(errno);
264 				warn("%s: %s", path, strerror(errno));
265 				goto err2;;
266 			}
267 	if (nr < 0) {
268 		e_to_sys(errno);
269 		warn("temporary file: %s", strerror(errno));
270 		goto err2;;
271 	}
272 
273 	/* Flush to disk, don't wait for update. */
274 	if (fsync(mbfd)) {
275 		e_to_sys(errno);
276 		warn("%s: %s", path, strerror(errno));
277 err2:		(void)ftruncate(mbfd, curoff);
278 err1:		(void)close(mbfd);
279 		return;
280 	}
281 
282 	/* Close and check -- NFS doesn't write until the close. */
283 	if (close(mbfd)) {
284 		e_to_sys(errno);
285 		warn("%s: %s", path, strerror(errno));
286 		return;
287 	}
288 
289 	notifybiff(biffmsg);
290 }
291 
292 void
293 notifybiff(msg)
294 	char *msg;
295 {
296 	static struct sockaddr_in addr;
297 	static int f = -1;
298 	struct hostent *hp;
299 	struct servent *sp;
300 	int len;
301 
302 	if (!addr.sin_family) {
303 		/* Be silent if biff service not available. */
304 		if (!(sp = getservbyname("biff", "udp")))
305 			return;
306 		if (!(hp = gethostbyname("localhost"))) {
307 			warn("localhost: %s", strerror(errno));
308 			return;
309 		}
310 		addr.sin_family = hp->h_addrtype;
311 		memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
312 		addr.sin_port = sp->s_port;
313 	}
314 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
315 		warn("socket: %s", strerror(errno));
316 		return;
317 	}
318 	len = strlen(msg) + 1;
319 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
320 	    != len)
321 		warn("sendto biff: %s", strerror(errno));
322 }
323 
324 void
325 usage()
326 {
327 	eval = EX_USAGE;
328 	err("usage: mail.local [-f from] user ...");
329 }
330 
331 #if __STDC__
332 void
333 err(const char *fmt, ...)
334 #else
335 void
336 err(fmt, va_alist)
337 	const char *fmt;
338 	va_dcl
339 #endif
340 {
341 	va_list ap;
342 
343 #if __STDC__
344 	va_start(ap, fmt);
345 #else
346 	va_start(ap);
347 #endif
348 	vwarn(fmt, ap);
349 	va_end(ap);
350 
351 	exit(eval);
352 }
353 
354 void
355 #if __STDC__
356 warn(const char *fmt, ...)
357 #else
358 warn(fmt, va_alist)
359 	const char *fmt;
360 	va_dcl
361 #endif
362 {
363 	va_list ap;
364 
365 #if __STDC__
366 	va_start(ap, fmt);
367 #else
368 	va_start(ap);
369 #endif
370 	vwarn(fmt, ap);
371 	va_end(ap);
372 }
373 
374 void
375 vwarn(fmt, ap)
376 	const char *fmt;
377 	_BSD_VA_LIST_ ap;
378 {
379 	/*
380 	 * Log the message to stderr.
381 	 *
382 	 * Don't use LOG_PERROR as an openlog() flag to do this,
383 	 * it's not portable enough.
384 	 */
385 	if (eval != EX_USAGE)
386 		(void)fprintf(stderr, "mail.local: ");
387 	(void)vfprintf(stderr, fmt, ap);
388 	(void)fprintf(stderr, "\n");
389 
390 	/* Log the message to syslog. */
391 	vsyslog(LOG_ERR, fmt, ap);
392 }
393 
394 /*
395  * e_to_sys --
396  *	Guess which errno's are temporary.  Gag me.
397  */
398 void
399 e_to_sys(num)
400 	int num;
401 {
402 	/* Temporary failures override hard errors. */
403 	if (eval == EX_TEMPFAIL)
404 		return;
405 
406 	switch(num) {		/* Hopefully temporary errors. */
407 #ifdef EAGAIN
408 	case EAGAIN:		/* Resource temporarily unavailable */
409 #endif
410 #ifdef EDQUOT
411 	case EDQUOT:		/* Disc quota exceeded */
412 #endif
413 #ifdef EBUSY
414 	case EBUSY:		/* Device busy */
415 #endif
416 #ifdef EPROCLIM
417 	case EPROCLIM:		/* Too many processes */
418 #endif
419 #ifdef EUSERS
420 	case EUSERS:		/* Too many users */
421 #endif
422 #ifdef ECONNABORTED
423 	case ECONNABORTED:	/* Software caused connection abort */
424 #endif
425 #ifdef ECONNREFUSED
426 	case ECONNREFUSED:	/* Connection refused */
427 #endif
428 #ifdef ECONNRESET
429 	case ECONNRESET:	/* Connection reset by peer */
430 #endif
431 #ifdef EDEADLK
432 	case EDEADLK:		/* Resource deadlock avoided */
433 #endif
434 #ifdef EFBIG
435 	case EFBIG:		/* File too large */
436 #endif
437 #ifdef EHOSTDOWN
438 	case EHOSTDOWN:		/* Host is down */
439 #endif
440 #ifdef EHOSTUNREACH
441 	case EHOSTUNREACH:	/* No route to host */
442 #endif
443 #ifdef EMFILE
444 	case EMFILE:		/* Too many open files */
445 #endif
446 #ifdef ENETDOWN
447 	case ENETDOWN:		/* Network is down */
448 #endif
449 #ifdef ENETRESET
450 	case ENETRESET:		/* Network dropped connection on reset */
451 #endif
452 #ifdef ENETUNREACH
453 	case ENETUNREACH:	/* Network is unreachable */
454 #endif
455 #ifdef ENFILE
456 	case ENFILE:		/* Too many open files in system */
457 #endif
458 #ifdef ENOBUFS
459 	case ENOBUFS:		/* No buffer space available */
460 #endif
461 #ifdef ENOMEM
462 	case ENOMEM:		/* Cannot allocate memory */
463 #endif
464 #ifdef ENOSPC
465 	case ENOSPC:		/* No space left on device */
466 #endif
467 #ifdef EROFS
468 	case EROFS:		/* Read-only file system */
469 #endif
470 #ifdef ESTALE
471 	case ESTALE:		/* Stale NFS file handle */
472 #endif
473 #ifdef ETIMEDOUT
474 	case ETIMEDOUT:		/* Connection timed out */
475 #endif
476 #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
477 	case EWOULDBLOCK:	/* Operation would block. */
478 #endif
479 		eval = EX_TEMPFAIL;
480 		break;
481 	default:
482 		eval = EX_UNAVAILABLE;
483 		break;
484 	}
485 }
486