xref: /freebsd/contrib/sendmail/mail.local/mail.local.c (revision 380a989b3223d455375b4fae70fd0b9bdd43bafb)
1 /*-
2  * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
3  * Copyright (c) 1990, 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * By using this file, you agree to the terms and conditions set
7  * forth in the LICENSE file which can be found at the top level of
8  * the sendmail distribution.
9  *
10  */
11 
12 #ifndef lint
13 static char copyright[] =
14 "@(#) Copyright (c) 1990, 1993, 1994\n\
15 	The Regents of the University of California.  All rights reserved.\n";
16 #endif /* not lint */
17 
18 #ifndef lint
19 static char sccsid[] = "@(#)mail.local.c	8.78 (Berkeley) 5/19/98";
20 #endif /* not lint */
21 
22 /*
23  * This is not intended to work on System V derived systems
24  * such as Solaris or HP-UX, since they use a totally different
25  * approach to mailboxes (essentially, they have a setgid program
26  * rather than setuid, and they rely on the ability to "give away"
27  * files to do their work).  IT IS NOT A BUG that this doesn't
28  * work on such architectures.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <sys/socket.h>
34 #include <sys/file.h>
35 
36 #include <netinet/in.h>
37 
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <netdb.h>
41 #include <pwd.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <syslog.h>
46 #include <time.h>
47 #include <unistd.h>
48 #ifdef EX_OK
49 # undef EX_OK		/* unistd.h may have another use for this */
50 #endif
51 #include <sysexits.h>
52 #include <ctype.h>
53 
54 #ifdef __STDC__
55 #include <stdarg.h>
56 #else
57 #include <varargs.h>
58 #endif
59 
60 #if (defined(sun) && defined(__svr4__)) || defined(__SVR4)
61 # define USE_LOCKF	1
62 # define USE_SETEUID	1
63 # define _PATH_MAILDIR	"/var/mail"
64 #endif
65 
66 #if (defined(sun) && !defined(__svr4__)) && !defined(__SVR4)
67 # ifdef __dead
68 #  undef __dead
69 #  define __dead
70 # endif
71 #endif
72 
73 #if defined(_AIX)
74 # define USE_LOCKF	1
75 # define USE_SETEUID	1
76 # define USE_VSYSLOG	0
77 #endif
78 
79 #if defined(__hpux)
80 # define USE_LOCKF	1
81 # define USE_SETRESUID	1
82 # define USE_VSYSLOG	0
83 # ifdef __dead
84 #  undef __dead
85 #  define __dead
86 # endif
87 #endif
88 
89 #if defined(_CRAY)
90 # if !defined(MAXPATHLEN)
91 #  define MAXPATHLEN PATHSIZE
92 # endif
93 # define USE_VSYSLOG   0
94 # define _PATH_MAILDIR	"/usr/spool/mail"
95 #endif
96 
97 #if defined(ultrix)
98 # define USE_VSYSLOG	0
99 #endif
100 
101 #if defined(__osf__)
102 # define USE_VSYSLOG	0
103 #endif
104 
105 #if defined(NeXT)
106 # include <libc.h>
107 # define _PATH_MAILDIR	"/usr/spool/mail"
108 # define __dead		/* empty */
109 # define S_IRUSR	S_IREAD
110 # define S_IWUSR	S_IWRITE
111 #endif
112 
113 #if defined(IRIX64) || defined(IRIX5) || defined(IRIX6)
114 # include <paths.h>
115 # define HASSTRERROR	1	/* has strerror(3) */
116 #endif
117 
118 /*
119  * If you don't have flock, you could try using lockf instead.
120  */
121 
122 #ifdef USE_LOCKF
123 # define flock(a, b)	lockf(a, b, 0)
124 # define LOCK_EX	F_LOCK
125 #endif
126 
127 #ifndef USE_VSYSLOG
128 # define USE_VSYSLOG	1
129 #endif
130 
131 #ifndef LOCK_EX
132 # include <sys/file.h>
133 #endif
134 
135 #if defined(BSD4_4) || defined(__GLIBC__)
136 # include "pathnames.h"
137 #endif
138 
139 #ifndef __P
140 # ifdef __STDC__
141 #  define __P(protos)	protos
142 # else
143 #  define __P(protos)	()
144 #  define const
145 # endif
146 #endif
147 #ifndef __dead
148 # if defined(__GNUC__) && (__GNUC__ < 2 || __GNUC_MINOR__ < 5) && !defined(__STRICT_ANSI__)
149 #  define __dead	__volatile
150 # else
151 #  define __dead
152 # endif
153 #endif
154 
155 #ifdef BSD4_4
156 # define HAS_ST_GEN	1
157 #else
158 # ifndef _BSD_VA_LIST_
159 #  define _BSD_VA_LIST_	va_list
160 # endif
161 #endif
162 
163 #if defined(BSD4_4) || defined(linux)
164 # define HASSNPRINTF	1
165 #else
166 # ifndef ultrix
167 extern FILE	*fdopen __P((int, const char *));
168 # endif
169 #endif
170 
171 #if SOLARIS >= 20600 || (SOLARIS < 10000 && SOLARIS >= 206)
172 # define HASSNPRINTF	1		/* has snprintf starting in 2.6 */
173 #endif
174 
175 #if !HASSNPRINTF
176 extern int	snprintf __P((char *, size_t, const char *, ...));
177 # ifndef _CRAY
178 extern int	vsnprintf __P((char *, size_t, const char *, ...));
179 # endif
180 #endif
181 
182 #if defined(BSD4_4) || defined(__osf__) || defined(__GNU_LIBRARY__)
183 # ifndef HASSTRERROR
184 #  define HASSTRERROR	1
185 # endif
186 #endif
187 
188 #if !HASSTRERROR
189 extern char	*strerror __P((int));
190 #endif
191 
192 /*
193  * If you don't have setreuid, and you have saved uids, and you have
194  * a seteuid() call that doesn't try to emulate using setuid(), then
195  * you can try defining USE_SETEUID.
196  */
197 #ifdef USE_SETEUID
198 # define setreuid(r, e)		seteuid(e)
199 #endif
200 
201 /*
202  * And of course on hpux you have setresuid()
203  */
204 #ifdef USE_SETRESUID
205 # define setreuid(r, e)		setresuid(-1, e, -1)
206 #endif
207 
208 #ifndef _PATH_LOCTMP
209 # define _PATH_LOCTMP	"/tmp/local.XXXXXX"
210 #endif
211 #ifndef _PATH_MAILDIR
212 # define _PATH_MAILDIR	"/var/spool/mail"
213 #endif
214 
215 #ifndef S_ISREG
216 # define S_ISREG(mode)	(((mode) & _S_IFMT) == S_IFREG)
217 #endif
218 
219 int	eval = EX_OK;			/* sysexits.h error value. */
220 int	lmtpmode = 0;
221 u_char	tTdvect[100];
222 
223 void		deliver __P((int, char *, int, int));
224 void		e_to_sys __P((int));
225 void		err __P((const char *, ...)) __dead2;
226 void		notifybiff __P((char *));
227 int		store __P((char *, int));
228 void		usage __P((void));
229 void		vwarn __P((const char *, _BSD_VA_LIST_));
230 void		warn __P((const char *, ...));
231 void		lockmbox __P((char *));
232 void		unlockmbox __P((void));
233 void		mailerr __P((const char *, const char *, ...));
234 void		dolmtp __P((int, int));
235 
236 int
237 main(argc, argv)
238 	int argc;
239 	char *argv[];
240 {
241 	struct passwd *pw;
242 	int ch, fd, nobiff, nofsync;
243 	uid_t uid;
244 	char *from;
245 	extern char *optarg;
246 	extern int optind;
247 
248 	/* make sure we have some open file descriptors */
249 	for (fd = 10; fd < 30; fd++)
250 		(void) close(fd);
251 
252 	/* use a reasonable umask */
253 	(void) umask(0077);
254 
255 #ifdef LOG_MAIL
256 	openlog("mail.local", 0, LOG_MAIL);
257 #else
258 	openlog("mail.local", 0);
259 #endif
260 
261 	from = NULL;
262 	nobiff = 0;
263 	nofsync = 0;
264 	while ((ch = getopt(argc, argv, "bdf:r:ls")) != -1)
265 		switch(ch) {
266 		case 'b':
267 			nobiff++;
268 			break;
269 		case 'd':		/* Backward compatible. */
270 			break;
271 		case 'f':
272 		case 'r':		/* Backward compatible. */
273 			if (from != NULL) {
274 				warn("multiple -f options");
275 				usage();
276 			}
277 			from = optarg;
278 			break;
279 		case 'l':
280 			lmtpmode++;
281 			break;
282 		case 's':
283 			nofsync++;
284 			break;
285 		case '?':
286 		default:
287 			usage();
288 		}
289 	argc -= optind;
290 	argv += optind;
291 
292 	if (lmtpmode)
293 		dolmtp(nobiff, nofsync);
294 
295 	if (!*argv)
296 		usage();
297 
298 	/*
299 	 * If from not specified, use the name from getlogin() if the
300 	 * uid matches, otherwise, use the name from the password file
301 	 * corresponding to the uid.
302 	 */
303 	uid = getuid();
304 	if (!from && (!(from = getlogin()) ||
305 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
306 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
307 
308 	/*
309 	 * There is no way to distinguish the error status of one delivery
310 	 * from the rest of the deliveries.  So, if we failed hard on one
311 	 * or more deliveries, but had no failures on any of the others, we
312 	 * return a hard failure.  If we failed temporarily on one or more
313 	 * deliveries, we return a temporary failure regardless of the other
314 	 * failures.  This results in the delivery being reattempted later
315 	 * at the expense of repeated failures and multiple deliveries.
316 	 */
317 	for (fd = store(from, 0); *argv; ++argv)
318 		deliver(fd, *argv, nobiff, nofsync);
319 	exit(eval);
320 }
321 
322 char *
323 parseaddr(s)
324 	char *s;
325 {
326 	char *p;
327 	int len;
328 
329 	if (*s++ != '<')
330 		return NULL;
331 
332 	p = s;
333 
334 	/* at-domain-list */
335 	while (*p == '@') {
336 		p++;
337 		if (*p == '[') {
338 			p++;
339 			while (isascii(*p) &&
340 			       (isalnum(*p) || *p == '.' ||
341 				*p == '-' || *p == ':'))
342 				p++;
343 			if (*p++ != ']')
344 				return NULL;
345 		} else {
346 			while ((isascii(*p) && isalnum(*p)) ||
347 			       *p == '.' || *p == '-')
348 				p++;
349 		}
350 		if (*p == ',' && p[1] == '@')
351 			p++;
352 		else if (*p == ':' && p[1] != '@')
353 			p++;
354 		else
355 			return NULL;
356 	}
357 
358 	/* local-part */
359 	if (*p == '\"') {
360 		p++;
361 		while (*p && *p != '\"') {
362 			if (*p == '\\') {
363 				if (!*++p)
364 					return NULL;
365 			}
366 			p++;
367 		}
368 		if (!*p++)
369 			return NULL;
370 	} else {
371 		while (*p && *p != '@' && *p != '>') {
372 			if (*p == '\\') {
373 				if (!*++p)
374 					return NULL;
375 			} else {
376 			if (*p <= ' ' || (*p & 128) ||
377 			    strchr("<>()[]\\,;:\"", *p))
378 				return NULL;
379 			}
380 			p++;
381 		}
382 	}
383 
384 	/* @domain */
385 	if (*p == '@') {
386 		p++;
387 		if (*p == '[') {
388 			p++;
389 			while (isascii(*p) &&
390 			       (isalnum(*p) || *p == '.' ||
391 				*p == '-' || *p == ':'))
392 				p++;
393 			if (*p++ != ']')
394 				return NULL;
395 		} else {
396 			while ((isascii(*p) && isalnum(*p)) ||
397 			       *p == '.' || *p == '-')
398 				p++;
399 		}
400 	}
401 
402 	if (*p++ != '>')
403 		return NULL;
404 	if (*p && *p != ' ')
405 		return NULL;
406 	len = p - s - 1;
407 
408 	p = malloc(len + 1);
409 	if (p == NULL) {
410 		printf("421 4.3.0 memory exhausted\r\n");
411 		exit(EX_TEMPFAIL);
412 	}
413 
414 	strncpy(p, s, len);
415 	p[len] = '\0';
416 	return p;
417 }
418 
419 char *
420 process_recipient(addr)
421 	char *addr;
422 {
423 	if (getpwnam(addr) == NULL) {
424 		return "550 5.1.1 user unknown";
425 	}
426 
427 	return NULL;
428 }
429 
430 
431 #define RCPT_GROW	30
432 
433 void
434 dolmtp(nobiff, nofsync)
435 	int nobiff, nofsync;
436 {
437 	char *return_path = NULL;
438 	char **rcpt_addr = NULL;
439 	int rcpt_num = 0;
440 	int rcpt_alloc = 0;
441 	char myhostname[1024];
442 	char buf[4096];
443 	char *err;
444 	int msgfd;
445 	char *p;
446 	int i;
447 
448 	gethostname(myhostname, sizeof myhostname - 1);
449 
450 	printf("220 %s LMTP ready\r\n", myhostname);
451 	for (;;) {
452 		fflush(stdout);
453 		if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
454 			exit(EX_OK);
455 		}
456 		p = buf + strlen(buf) - 1;
457 		if (p >= buf && *p == '\n')
458 			*p-- = '\0';
459 		if (p >= buf && *p == '\r')
460 			*p-- = '\0';
461 
462 		switch (buf[0]) {
463 
464 		case 'd':
465 		case 'D':
466 			if (strcasecmp(buf, "data") == 0) {
467 				if (rcpt_num == 0) {
468 					printf("503 5.5.1 No recipients\r\n");
469 					continue;
470 				}
471 				msgfd = store(return_path, rcpt_num);
472 				if (msgfd == -1)
473 					continue;
474 
475 				for (i = 0; i < rcpt_num; i++) {
476 					p = strchr(rcpt_addr[i], '+');
477 					if (p != NULL)
478 						*p++ = '\0';
479 					deliver(msgfd, rcpt_addr[i], nobiff,
480 					    nofsync);
481 				}
482 				close(msgfd);
483 				goto rset;
484 			}
485 			goto syntaxerr;
486 
487 		case 'l':
488 		case 'L':
489 			if (strncasecmp(buf, "lhlo ", 5) == 0) {
490 				printf("250-%s\r\n250-8BITMIME\r\n250-ENHANCEDSTATUSCODES\r\n250 PIPELINING\r\n",
491 					   myhostname);
492 				continue;
493 			}
494 			goto syntaxerr;
495 
496 		case 'm':
497 		case 'M':
498 			if (strncasecmp(buf, "mail ", 5) == 0) {
499 				if (return_path != NULL) {
500 					printf("503 5.5.1 Nested MAIL command\r\n");
501 					continue;
502 				}
503 				if (strncasecmp(buf+5, "from:", 5) != 0 ||
504 				    ((return_path = parseaddr(buf+10)) == NULL)) {
505 					printf("501 5.5.4 Syntax error in parameters\r\n");
506 					continue;
507 				}
508 				printf("250 2.5.0 ok\r\n");
509 				continue;
510 			}
511 			goto syntaxerr;
512 
513 		case 'n':
514 		case 'N':
515 			if (strcasecmp(buf, "noop") == 0) {
516 				printf("250 2.0.0 ok\r\n");
517 				continue;
518 			}
519 			goto syntaxerr;
520 
521 		case 'q':
522 		case 'Q':
523 			if (strcasecmp(buf, "quit") == 0) {
524 				printf("221 2.0.0 bye\r\n");
525 				exit(EX_OK);
526 			}
527 			goto syntaxerr;
528 
529 		case 'r':
530 		case 'R':
531 			if (strncasecmp(buf, "rcpt ", 5) == 0) {
532 				if (return_path == NULL) {
533 					printf("503 5.5.1 Need MAIL command\r\n");
534 					continue;
535 				}
536 				if (rcpt_num >= rcpt_alloc) {
537 					rcpt_alloc += RCPT_GROW;
538 					rcpt_addr = (char **)
539 						realloc((char *)rcpt_addr,
540 							rcpt_alloc * sizeof(char **));
541 					if (rcpt_addr == NULL) {
542 						printf("421 4.3.0 memory exhausted\r\n");
543 						exit(EX_TEMPFAIL);
544 					}
545 				}
546 				if (strncasecmp(buf+5, "to:", 3) != 0 ||
547 				    ((rcpt_addr[rcpt_num] = parseaddr(buf+8)) == NULL)) {
548 					printf("501 5.5.4 Syntax error in parameters\r\n");
549 					continue;
550 				}
551 				if ((err = process_recipient(rcpt_addr[rcpt_num])) != NULL) {
552 					printf("%s\r\n", err);
553 					continue;
554 				}
555 				rcpt_num++;
556 				printf("250 2.1.5 ok\r\n");
557 				continue;
558 			}
559 			else if (strcasecmp(buf, "rset") == 0) {
560 				printf("250 2.0.0 ok\r\n");
561 
562   rset:
563 				while (rcpt_num) {
564 					free(rcpt_addr[--rcpt_num]);
565 				}
566 				if (return_path != NULL)
567 					free(return_path);
568 				return_path = NULL;
569 				continue;
570 			}
571 			goto syntaxerr;
572 
573 		case 'v':
574 		case 'V':
575 			if (strncasecmp(buf, "vrfy ", 5) == 0) {
576 				printf("252 2.3.3 try RCPT to attempt delivery\r\n");
577 				continue;
578 			}
579 			goto syntaxerr;
580 
581 		default:
582   syntaxerr:
583 			printf("500 5.5.2 Syntax error\r\n");
584 			continue;
585 		}
586 	}
587 }
588 
589 int
590 store(from, lmtprcpts)
591 	char *from;
592 	int lmtprcpts;
593 {
594 	FILE *fp;
595 	time_t tval;
596 	int fd, eline;
597 	char line[2048];
598 	char tmpbuf[sizeof _PATH_LOCTMP + 1];
599 
600 	strcpy(tmpbuf, _PATH_LOCTMP);
601 	if ((fd = mkstemp(tmpbuf)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
602 		if (lmtprcpts) {
603 			printf("451 4.3.0 unable to open temporary file\r\n");
604 			return -1;
605 		} else {
606 			e_to_sys(errno);
607 			err("unable to open temporary file");
608 		}
609 	}
610 	(void)unlink(tmpbuf);
611 
612 	if (lmtpmode) {
613 		printf("354 go ahead\r\n");
614 		fflush(stdout);
615 	}
616 
617 	(void)time(&tval);
618 	(void)fprintf(fp, "From %s %s", from, ctime(&tval));
619 
620 	line[0] = '\0';
621 	for (eline = 1; fgets(line, sizeof(line), stdin);) {
622 		if (line[strlen(line)-2] == '\r') {
623 			strcpy(line+strlen(line)-2, "\n");
624 		}
625 		if (lmtprcpts && line[0] == '.') {
626 			if (line[1] == '\n')
627 				goto lmtpdot;
628 			strcpy(line, line+1);
629 		}
630 		if (line[0] == '\n')
631 			eline = 1;
632 		else {
633 			if (eline && line[0] == 'F' &&
634 			    !memcmp(line, "From ", 5))
635 				(void)putc('>', fp);
636 			eline = 0;
637 		}
638 		(void)fprintf(fp, "%s", line);
639 		if (ferror(fp)) {
640 			if (lmtprcpts) {
641 				while (lmtprcpts--) {
642 					printf("451 4.3.0 temporary file write error\r\n");
643 				}
644 				fclose(fp);
645 				return -1;
646 			} else {
647 				e_to_sys(errno);
648 				err("temporary file write error");
649 			}
650 		}
651 	}
652 
653 	if (lmtprcpts) {
654 		/* Got a premature EOF -- toss message and exit */
655 		exit(EX_OK);
656 	}
657 
658 	/* If message not newline terminated, need an extra. */
659 	if (strchr(line, '\n') == NULL)
660 		(void)putc('\n', fp);
661 
662   lmtpdot:
663 
664 	/* Output a newline; note, empty messages are allowed. */
665 	(void)putc('\n', fp);
666 
667 	if (fflush(fp) == EOF || ferror(fp)) {
668 		if (lmtprcpts) {
669 			while (lmtprcpts--) {
670 				printf("451 4.3.0 temporary file write error\r\n");
671 			}
672 			fclose(fp);
673 			return -1;
674 		} else {
675 			e_to_sys(errno);
676 			err("temporary file write error");
677 		}
678 	}
679 	return (fd);
680 }
681 
682 void
683 deliver(fd, name, nobiff, nofsync)
684 	int fd;
685 	char *name;
686 	int nobiff, nofsync;
687 {
688 	struct stat fsb, sb;
689 	struct passwd *pw;
690 	int mbfd, nr, nw, off;
691 	char *p;
692 	char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
693 	off_t curoff;
694 	extern char *quad_to_string();
695 
696 	/*
697 	 * Disallow delivery to unknown names -- special mailboxes can be
698 	 * handled in the sendmail aliases file.
699 	 */
700 	if ((pw = getpwnam(name)) == NULL) {
701 		if (eval != EX_TEMPFAIL)
702 			eval = EX_UNAVAILABLE;
703 		if (lmtpmode) {
704 			if (eval == EX_TEMPFAIL) {
705 				printf("451 4.3.0 cannot lookup name: %s\r\n", name);
706 			} else {
707 				printf("550 5.1.1 unknown name: %s\r\n", name);
708 			}
709 		}
710 		else {
711 			warn("unknown name: %s", name);
712 		}
713 		return;
714 	}
715 	endpwent();
716 
717 	/*
718 	 * Keep name reasonably short to avoid buffer overruns.
719 	 *	This isn't necessary on BSD because of the proper
720 	 *	definition of snprintf(), but it can cause problems
721 	 *	on other systems.
722 	 * Also, clear out any bogus characters.
723 	 */
724 
725 	if (strlen(name) > 40)
726 		name[40] = '\0';
727 	for (p = name; *p != '\0'; p++)
728 	{
729 		if (!isascii(*p))
730 			*p &= 0x7f;
731 		else if (!isprint(*p))
732 			*p = '.';
733 	}
734 
735 	(void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
736 
737 	/*
738 	 * If the mailbox is linked or a symlink, fail.  There's an obvious
739 	 * race here, that the file was replaced with a symbolic link after
740 	 * the lstat returned, but before the open.  We attempt to detect
741 	 * this by comparing the original stat information and information
742 	 * returned by an fstat of the file descriptor returned by the open.
743 	 *
744 	 * NB: this is a symptom of a larger problem, that the mail spooling
745 	 * directory is writeable by the wrong users.  If that directory is
746 	 * writeable, system security is compromised for other reasons, and
747 	 * it cannot be fixed here.
748 	 *
749 	 * If we created the mailbox, set the owner/group.  If that fails,
750 	 * just return.  Another process may have already opened it, so we
751 	 * can't unlink it.  Historically, binmail set the owner/group at
752 	 * each mail delivery.  We no longer do this, assuming that if the
753 	 * ownership or permissions were changed there was a reason.
754 	 *
755 	 * XXX
756 	 * open(2) should support flock'ing the file.
757 	 */
758 tryagain:
759 	lockmbox(path);
760 	if (lstat(path, &sb) < 0) {
761 		mbfd = open(path,
762 			O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
763 		if (lstat(path, &sb) < 0)
764 		{
765 			eval = EX_CANTCREAT;
766 			warn("%s: lstat: file changed after open", path);
767 			goto err1;
768 		}
769 		else
770 			sb.st_uid = pw->pw_uid;
771 		if (mbfd == -1) {
772 			if (errno == EEXIST)
773 				goto tryagain;
774 		} else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
775 			mailerr("451 4.3.0", "chown %u.%u: %s",
776 				pw->pw_uid, pw->pw_gid, name);
777 			goto err1;
778 		}
779 	} else if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) {
780 		mailerr("550 5.2.0", "%s: irregular file", path);
781 		goto err0;
782 	} else if (sb.st_uid != pw->pw_uid) {
783 		eval = EX_CANTCREAT;
784 		mailerr("550 5.2.0", "%s: wrong ownership (%d)",
785 				path, sb.st_uid);
786 		goto err0;
787 	} else {
788 		mbfd = open(path, O_APPEND|O_WRONLY, 0);
789 	}
790 
791 	if (mbfd == -1) {
792 		mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
793 		goto err0;
794 	} else if (fstat(mbfd, &fsb) < 0 ||
795 	    fsb.st_nlink != 1 ||
796 	    sb.st_nlink != 1 ||
797 	    !S_ISREG(fsb.st_mode) ||
798 	    sb.st_dev != fsb.st_dev ||
799 	    sb.st_ino != fsb.st_ino ||
800 #if HAS_ST_GEN && 0		/* AFS returns random values for st_gen */
801 	    sb.st_gen != fsb.st_gen ||
802 #endif
803 	    sb.st_uid != fsb.st_uid) {
804 		eval = EX_TEMPFAIL;
805 		warn("%s: fstat: file changed after open", path);
806 		goto err1;
807 	}
808 
809 	/* Wait until we can get a lock on the file. */
810 	if (flock(mbfd, LOCK_EX)) {
811 		mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
812 		goto err1;
813 	}
814 
815 	if (!nobiff) {
816 		/* Get the starting offset of the new message for biff. */
817 		curoff = lseek(mbfd, (off_t)0, SEEK_END);
818 		if (sizeof curoff > sizeof(long))
819 			(void)snprintf(biffmsg, sizeof(biffmsg), "%s@%s\n",
820 				       name, quad_to_string(curoff));
821 		else
822 			(void)snprintf(biffmsg, sizeof(biffmsg), "%s@%ld\n",
823 				       name, curoff);
824 	}
825 
826 	/* Copy the message into the file. */
827 	if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
828 		mailerr("450 4.2.0", "temporary file: %s",
829 			strerror(errno));
830 		goto err1;
831 	}
832 	if (setreuid(0, pw->pw_uid) < 0) {
833 		mailerr("450 4.2.0", "setreuid(0, %d): %s (r=%d, e=%d)",
834 		     pw->pw_uid, strerror(errno), getuid(), geteuid());
835 		goto err1;
836 	}
837 #ifdef DEBUG
838 	printf("new euid = %d\n", geteuid());
839 #endif
840 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
841 		for (off = 0; off < nr; off += nw)
842 			if ((nw = write(mbfd, buf + off, nr - off)) < 0) {
843 				mailerr("450 4.2.0", "%s: %s",
844 					path, strerror(errno));
845 				goto err3;
846 			}
847 	if (nr < 0) {
848 		mailerr("450 4.2.0", "temporary file: %s",
849 			strerror(errno));
850 		goto err3;
851 	}
852 
853 	/* Flush to disk, don't wait for update. */
854 	if (!nofsync && fsync(mbfd)) {
855 		mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
856 err3:
857 		if (setreuid(0, 0) < 0) {
858 			e_to_sys(errno);
859 			mailerr("450 4.2.0", "setreuid(0, 0): %s",
860 				strerror(errno));
861 		}
862 #ifdef DEBUG
863 		printf("reset euid = %d\n", geteuid());
864 #endif
865 		(void)ftruncate(mbfd, curoff);
866 err1:		(void)close(mbfd);
867 err0:		unlockmbox();
868 		return;
869 	}
870 
871 	/* Close and check -- NFS doesn't write until the close. */
872 	if (close(mbfd)) {
873 		mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
874 		truncate(path, curoff);
875 	} else if (!nobiff)
876 		notifybiff(biffmsg);
877 
878 	if (setreuid(0, 0) < 0) {
879 		mailerr("450 4.2.0", "setreuid(0, 0): %s",
880 			strerror(errno));
881 		goto err0;
882 	}
883 #ifdef DEBUG
884 	printf("reset euid = %d\n", geteuid());
885 #endif
886 	unlockmbox();
887 	if (lmtpmode) {
888 		printf("250 2.1.5 %s OK\r\n", name);
889 	}
890 }
891 
892 /*
893  * user.lock files are necessary for compatibility with other
894  * systems, e.g., when the mail spool file is NFS exported.
895  * Alas, mailbox locking is more than just a local matter.
896  * EPA 11/94.
897  */
898 
899 char	lockname[MAXPATHLEN];
900 int	locked = 0;
901 
902 void
903 lockmbox(path)
904 	char *path;
905 {
906 	int statfailed = 0;
907 
908 	if (locked)
909 		return;
910 	if (strlen(path) + 6 > sizeof lockname)
911 		return;
912 	snprintf(lockname, sizeof lockname, "%s.lock", path);
913 	for (;; sleep(5)) {
914 		int fd;
915 		struct stat st;
916 		time_t now;
917 
918 		fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0);
919 		if (fd >= 0) {
920 			/* defeat lock checking programs which test pid */
921 			write(fd, "0", 2);
922 			locked = 1;
923 			close(fd);
924 			return;
925 		}
926 		if (stat(lockname, &st) < 0) {
927 			if (statfailed++ > 5)
928 				return;
929 			continue;
930 		}
931 		statfailed = 0;
932 		time(&now);
933 		if (now < st.st_ctime + 300)
934 			continue;
935 		unlink(lockname);
936 	}
937 }
938 
939 void
940 unlockmbox()
941 {
942 	if (!locked)
943 		return;
944 	unlink(lockname);
945 	locked = 0;
946 }
947 
948 void
949 notifybiff(msg)
950 	char *msg;
951 {
952 	static struct sockaddr_in addr;
953 	static int f = -1;
954 	struct hostent *hp;
955 	struct servent *sp;
956 	int len;
957 
958 	if (addr.sin_family == 0) {
959 		/* Be silent if biff service not available. */
960 		if ((sp = getservbyname("biff", "udp")) == NULL)
961 			return;
962 		if ((hp = gethostbyname("localhost")) == NULL) {
963 			warn("localhost: %s", strerror(errno));
964 			return;
965 		}
966 		addr.sin_family = hp->h_addrtype;
967 		memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
968 		addr.sin_port = sp->s_port;
969 	}
970 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
971 		warn("socket: %s", strerror(errno));
972 		return;
973 	}
974 	len = strlen(msg) + 1;
975 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
976 	    != len)
977 		warn("sendto biff: %s", strerror(errno));
978 }
979 
980 void
981 usage()
982 {
983 	eval = EX_USAGE;
984 	err("usage: mail.local [-b] [-l] [-f from] [-s] user ...");
985 }
986 
987 void
988 #ifdef __STDC__
989 mailerr(const char *hdr, const char *fmt, ...)
990 #else
991 mailerr(hdr, fmt, va_alist)
992 	const char *hdr;
993 	const char *fmt;
994 	va_dcl
995 #endif
996 {
997 	va_list ap;
998 
999 #ifdef __STDC__
1000 	va_start(ap, fmt);
1001 #else
1002 	va_start(ap);
1003 #endif
1004 	if (lmtpmode)
1005 	{
1006 		printf("%s ", hdr);
1007 		vprintf(fmt, ap);
1008 		printf("\r\n");
1009 	}
1010 	else
1011 	{
1012 		e_to_sys(errno);
1013 		vwarn(fmt, ap);
1014 	}
1015 }
1016 
1017 #ifdef __STDC__
1018 void
1019 err(const char *fmt, ...)
1020 #else
1021 void
1022 err(fmt, va_alist)
1023 	const char *fmt;
1024 	va_dcl
1025 #endif
1026 {
1027 	va_list ap;
1028 
1029 #ifdef __STDC__
1030 	va_start(ap, fmt);
1031 #else
1032 	va_start(ap);
1033 #endif
1034 	vwarn(fmt, ap);
1035 	va_end(ap);
1036 
1037 	exit(eval);
1038 }
1039 
1040 void
1041 #ifdef __STDC__
1042 warn(const char *fmt, ...)
1043 #else
1044 warn(fmt, va_alist)
1045 	const char *fmt;
1046 	va_dcl
1047 #endif
1048 {
1049 	va_list ap;
1050 
1051 #ifdef __STDC__
1052 	va_start(ap, fmt);
1053 #else
1054 	va_start(ap);
1055 #endif
1056 	vwarn(fmt, ap);
1057 	va_end(ap);
1058 }
1059 
1060 void
1061 vwarn(fmt, ap)
1062 	const char *fmt;
1063 	_BSD_VA_LIST_ ap;
1064 {
1065 	/*
1066 	 * Log the message to stderr.
1067 	 *
1068 	 * Don't use LOG_PERROR as an openlog() flag to do this,
1069 	 * it's not portable enough.
1070 	 */
1071 	if (eval != EX_USAGE)
1072 		(void)fprintf(stderr, "mail.local: ");
1073 	(void)vfprintf(stderr, fmt, ap);
1074 	(void)fprintf(stderr, "\n");
1075 
1076 #if USE_VSYSLOG
1077 	/* Log the message to syslog. */
1078 	vsyslog(LOG_ERR, fmt, ap);
1079 #else
1080 	{
1081 		char fmtbuf[10240];
1082 
1083 		(void) vsnprintf(fmtbuf, sizeof fmtbuf, fmt, ap);
1084 		syslog(LOG_ERR, "%s", fmtbuf);
1085 	}
1086 #endif
1087 }
1088 
1089 /*
1090  * e_to_sys --
1091  *	Guess which errno's are temporary.  Gag me.
1092  */
1093 void
1094 e_to_sys(num)
1095 	int num;
1096 {
1097 	/* Temporary failures override hard errors. */
1098 	if (eval == EX_TEMPFAIL)
1099 		return;
1100 
1101 	switch(num) {		/* Hopefully temporary errors. */
1102 #ifdef EAGAIN
1103 	case EAGAIN:		/* Resource temporarily unavailable */
1104 #endif
1105 #ifdef EDQUOT
1106 	case EDQUOT:		/* Disc quota exceeded */
1107 #endif
1108 #ifdef EBUSY
1109 	case EBUSY:		/* Device busy */
1110 #endif
1111 #ifdef EPROCLIM
1112 	case EPROCLIM:		/* Too many processes */
1113 #endif
1114 #ifdef EUSERS
1115 	case EUSERS:		/* Too many users */
1116 #endif
1117 #ifdef ECONNABORTED
1118 	case ECONNABORTED:	/* Software caused connection abort */
1119 #endif
1120 #ifdef ECONNREFUSED
1121 	case ECONNREFUSED:	/* Connection refused */
1122 #endif
1123 #ifdef ECONNRESET
1124 	case ECONNRESET:	/* Connection reset by peer */
1125 #endif
1126 #ifdef EDEADLK
1127 	case EDEADLK:		/* Resource deadlock avoided */
1128 #endif
1129 #ifdef EFBIG
1130 	case EFBIG:		/* File too large */
1131 #endif
1132 #ifdef EHOSTDOWN
1133 	case EHOSTDOWN:		/* Host is down */
1134 #endif
1135 #ifdef EHOSTUNREACH
1136 	case EHOSTUNREACH:	/* No route to host */
1137 #endif
1138 #ifdef EMFILE
1139 	case EMFILE:		/* Too many open files */
1140 #endif
1141 #ifdef ENETDOWN
1142 	case ENETDOWN:		/* Network is down */
1143 #endif
1144 #ifdef ENETRESET
1145 	case ENETRESET:		/* Network dropped connection on reset */
1146 #endif
1147 #ifdef ENETUNREACH
1148 	case ENETUNREACH:	/* Network is unreachable */
1149 #endif
1150 #ifdef ENFILE
1151 	case ENFILE:		/* Too many open files in system */
1152 #endif
1153 #ifdef ENOBUFS
1154 	case ENOBUFS:		/* No buffer space available */
1155 #endif
1156 #ifdef ENOMEM
1157 	case ENOMEM:		/* Cannot allocate memory */
1158 #endif
1159 #ifdef ENOSPC
1160 	case ENOSPC:		/* No space left on device */
1161 #endif
1162 #ifdef EROFS
1163 	case EROFS:		/* Read-only file system */
1164 #endif
1165 #ifdef ESTALE
1166 	case ESTALE:		/* Stale NFS file handle */
1167 #endif
1168 #ifdef ETIMEDOUT
1169 	case ETIMEDOUT:		/* Connection timed out */
1170 #endif
1171 #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN && EWOULDBLOCK != EDEADLK
1172 	case EWOULDBLOCK:	/* Operation would block. */
1173 #endif
1174 		eval = EX_TEMPFAIL;
1175 		break;
1176 	default:
1177 		eval = EX_UNAVAILABLE;
1178 		break;
1179 	}
1180 }
1181 
1182 #if !HASSTRERROR
1183 
1184 char *
1185 strerror(eno)
1186 	int eno;
1187 {
1188 	extern int sys_nerr;
1189 	extern char *sys_errlist[];
1190 	static char ebuf[60];
1191 
1192 	if (eno >= 0 && eno < sys_nerr)
1193 		return sys_errlist[eno];
1194 	(void) sprintf(ebuf, "Error %d", eno);
1195 	return ebuf;
1196 }
1197 
1198 #endif /* !HASSTRERROR */
1199 
1200 #if defined(ultrix) || defined(_CRAY)
1201 
1202 /*
1203  * Copyright (c) 1987, 1993
1204  *	The Regents of the University of California.  All rights reserved.
1205  *
1206  * Redistribution and use in source and binary forms, with or without
1207  * modification, are permitted provided that the following conditions
1208  * are met:
1209  * 1. Redistributions of source code must retain the above copyright
1210  *    notice, this list of conditions and the following disclaimer.
1211  * 2. Redistributions in binary form must reproduce the above copyright
1212  *    notice, this list of conditions and the following disclaimer in the
1213  *    documentation and/or other materials provided with the distribution.
1214  * 3. All advertising materials mentioning features or use of this software
1215  *    must display the following acknowledgement:
1216  *	This product includes software developed by the University of
1217  *	California, Berkeley and its contributors.
1218  * 4. Neither the name of the University nor the names of its contributors
1219  *    may be used to endorse or promote products derived from this software
1220  *    without specific prior written permission.
1221  *
1222  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
1223  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1224  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1225  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
1226  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1227  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
1228  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
1229  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
1230  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
1231  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
1232  * SUCH DAMAGE.
1233  */
1234 
1235 #if defined(LIBC_SCCS) && !defined(lint)
1236 static char sccsid[] = "@(#)mktemp.c	8.1 (Berkeley) 6/4/93";
1237 #endif /* LIBC_SCCS and not lint */
1238 
1239 #include <sys/types.h>
1240 #include <sys/stat.h>
1241 #include <fcntl.h>
1242 #include <errno.h>
1243 #include <stdio.h>
1244 #include <ctype.h>
1245 
1246 static int _gettemp();
1247 
1248 mkstemp(path)
1249 	char *path;
1250 {
1251 	int fd;
1252 
1253 	return (_gettemp(path, &fd) ? fd : -1);
1254 }
1255 
1256 /*
1257 char *
1258 mktemp(path)
1259 	char *path;
1260 {
1261 	return(_gettemp(path, (int *)NULL) ? path : (char *)NULL);
1262 }
1263 */
1264 
1265 static
1266 _gettemp(path, doopen)
1267 	char *path;
1268 	register int *doopen;
1269 {
1270 	extern int errno;
1271 	register char *start, *trv;
1272 	struct stat sbuf;
1273 	u_int pid;
1274 
1275 	pid = getpid();
1276 	for (trv = path; *trv; ++trv);		/* extra X's get set to 0's */
1277 	while (*--trv == 'X') {
1278 		*trv = (pid % 10) + '0';
1279 		pid /= 10;
1280 	}
1281 
1282 	/*
1283 	 * check the target directory; if you have six X's and it
1284 	 * doesn't exist this runs for a *very* long time.
1285 	 */
1286 	for (start = trv + 1;; --trv) {
1287 		if (trv <= path)
1288 			break;
1289 		if (*trv == '/') {
1290 			*trv = '\0';
1291 			if (stat(path, &sbuf) < 0)
1292 				return(0);
1293 			if (!S_ISDIR(sbuf.st_mode)) {
1294 				errno = ENOTDIR;
1295 				return(0);
1296 			}
1297 			*trv = '/';
1298 			break;
1299 		}
1300 	}
1301 
1302 	for (;;) {
1303 		if (doopen) {
1304 			if ((*doopen =
1305 			    open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0)
1306 				return(1);
1307 			if (errno != EEXIST)
1308 				return(0);
1309 		}
1310 		else if (stat(path, &sbuf) < 0)
1311 			return(errno == ENOENT ? 1 : 0);
1312 
1313 		/* tricky little algorithm for backward compatibility */
1314 		for (trv = start;;) {
1315 			if (!*trv)
1316 				return(0);
1317 			if (*trv == 'z')
1318 				*trv++ = 'a';
1319 			else {
1320 				if (isascii(*trv) && isdigit(*trv))
1321 					*trv = 'a';
1322 				else
1323 					++*trv;
1324 				break;
1325 			}
1326 		}
1327 	}
1328 	/*NOTREACHED*/
1329 }
1330 
1331 #endif /* ultrix */
1332