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