xref: /illumos-gate/usr/src/cmd/sendmail/util/mail.local.c (revision d3b5f56344d8bfcdd6cfb82446af0e5e55ad9ebe)
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
8  * of the sendmail distribution.
9  */
10 
11 /*
12  * Copyright 1994-2007 Sun Microsystems, Inc.  All rights reserved.
13  * Use is subject to license terms.
14  */
15 
16 #ifndef lint
17 static char copyright[] =
18 "@(#) Copyright (c) 1990, 1993, 1994\n\
19 	The Regents of the University of California.  All rights reserved.\n";
20 #endif /* not lint */
21 
22 #pragma ident  "%Z%%M% %I%     %E% SMI"
23 
24 #ifndef lint
25 static char sccsid[] = "@(#)mail.local.c	8.83 (Berkeley) 12/17/98";
26 static char sccsi2[] = "%W% (Sun) %G%";
27 #endif /* not lint */
28 
29 #include <sys/param.h>
30 #include <sys/stat.h>
31 #include <sys/socket.h>
32 #include <sys/file.h>
33 
34 #include <netinet/in.h>
35 
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <netdb.h>
39 #include <pwd.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <signal.h>
43 #include <ctype.h>
44 #include <string.h>
45 #include <sysexits.h>
46 #include <time.h>
47 #include <unistd.h>
48 #include <maillock.h>
49 #include <grp.h>
50 
51 #ifdef __STDC__
52 #include <stdarg.h>
53 #else
54 #include <varargs.h>
55 #endif
56 
57 #include <syslog.h>
58 
59 #include <sysexits.h>
60 #include <ctype.h>
61 
62 #include <sm/conf.h>
63 #include <sendmail/pathnames.h>
64 
65 /*
66 **  If you don't have flock, you could try using lockf instead.
67 */
68 
69 #ifdef LDA_USE_LOCKF
70 # define flock(a, b)	lockf(a, b, 0)
71 # ifdef LOCK_EX
72 #  undef LOCK_EX
73 # endif /* LOCK_EX */
74 # define LOCK_EX        F_LOCK
75 #endif /* LDA_USE_LOCKF */
76 
77 #ifndef LOCK_EX
78 # include <sys/file.h>
79 #endif /* ! LOCK_EX */
80 
81 #ifndef MAILER_DAEMON
82 # define MAILER_DAEMON	"MAILER-DAEMON"
83 #endif
84 
85 typedef int bool;
86 
87 #define	FALSE	0
88 #define	TRUE	1
89 
90 bool	EightBitMime = TRUE;		/* advertise 8BITMIME in LMTP */
91 static int eval = EX_OK;			/* sysexits.h error value. */
92 static int lmtpmode = 0;
93 bool	bouncequota = FALSE;		/* permanent error when over quota */
94 
95 #define	_PATH_MAILDIR	"/var/mail"
96 #define	_PATH_LOCTMP	"/tmp/local.XXXXXX"
97 #define	_PATH_LOCHTMP	"/tmp/lochd.XXXXXX"
98 #define	FALSE 0
99 #define	TRUE  1
100 #define	MAXLINE 2048
101 
102 static void	deliver(int, int, char *, bool);
103 static void	e_to_sys(int);
104 static void	err(const char *fmt, ...);
105 static void	notifybiff(char *);
106 static void	store(char *, int);
107 static void	usage(void);
108 static void	vwarn();
109 static void	warn(const char *fmt, ...);
110 static void	mailerr(const char *, const char *, ...);
111 static void	sigterm_handler();
112 
113 static char	unix_from_line[MAXLINE];
114 static int	ulen;
115 static int	content_length;
116 static int	bfd, hfd; /* temp file */
117 static uid_t	src_uid, targ_uid, saved_uid;
118 static int	sigterm_caught;
119 
120 int
121 main(argc, argv)
122 	int argc;
123 	char *argv[];
124 {
125 	struct passwd *pw;
126 	int ch;
127 	uid_t uid;
128 	char *from;
129 	struct  group *grpptr;
130 	void dolmtp();
131 
132 	openlog("mail.local", 0, LOG_MAIL);
133 
134 	from = NULL;
135 	pw = NULL;
136 	sigterm_caught = FALSE;
137 
138 	(void) sigset(SIGTERM, sigterm_handler);
139 
140 	while ((ch = getopt(argc, argv, "7bdf:r:l")) != EOF)
141 		switch (ch) {
142 		case '7':		/* Do not advertise 8BITMIME */
143 			EightBitMime = FALSE;
144 			break;
145 
146 		case 'b':		/* bounce mail when over quota. */
147 			bouncequota = TRUE;
148 			break;
149 
150 		case 'd':		/* Backward compatible. */
151 			break;
152 		case 'f':
153 		case 'r':		/* Backward compatible. */
154 			if (from != NULL) {
155 				warn("multiple -f options");
156 				usage();
157 			}
158 			from = optarg;
159 			break;
160 		case 'l':
161 			lmtpmode++;
162 			break;
163 		case '?':
164 		default:
165 			usage();
166 		}
167 	argc -= optind;
168 	argv += optind;
169 
170 	notifybiff(NULL); /* initialize biff structures */
171 
172 	/*
173 	 * We expect sendmail will invoke us with saved id 0
174 	 * We then do setgid and setuid defore delivery
175 	 * setgid to mail group
176 	 */
177 	if ((grpptr = getgrnam("mail")) != NULL)
178 		(void) setgid(grpptr->gr_gid);
179 	saved_uid = geteuid();
180 
181 	if (lmtpmode) {
182 		if (saved_uid != 0) {
183 			warn("only super-user can use -l option");
184 			exit(EX_CANTCREAT);
185 		}
186 		dolmtp(bouncequota);
187 	}
188 
189 	if (!*argv)
190 		usage();
191 
192 	/*
193 	 * If from not specified, use the name from getlogin() if the
194 	 * uid matches, otherwise, use the name from the password file
195 	 * corresponding to the uid.
196 	 */
197 	uid = getuid();
198 	if (!from && (!(from = getlogin()) ||
199 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
200 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
201 	src_uid = pw ? pw->pw_uid : uid;
202 
203 	/*
204 	 * There is no way to distinguish the error status of one delivery
205 	 * from the rest of the deliveries.  So, if we failed hard on one
206 	 * or more deliveries, but had no failures on any of the others, we
207 	 * return a hard failure.  If we failed temporarily on one or more
208 	 * deliveries, we return a temporary failure regardless of the other
209 	 * failures.  This results in the delivery being reattempted later
210 	 * at the expense of repeated failures and multiple deliveries.
211 	 */
212 
213 	for (store(from, 0); *argv; ++argv)
214 		deliver(hfd, bfd, *argv, bouncequota);
215 	return (eval);
216 }
217 
218 void
219 sigterm_handler()
220 {
221 	sigterm_caught = TRUE;
222 	(void) sigignore(SIGTERM);
223 }
224 
225 char *
226 parseaddr(s)
227 	char *s;
228 {
229 	char *p;
230 	int len;
231 
232 	if (*s++ != '<')
233 		return NULL;
234 
235 	p = s;
236 
237 	/* at-domain-list */
238 	while (*p == '@') {
239 		p++;
240 		if (*p == '[') {
241 			p++;
242 			while (isascii(*p) &&
243 			       (isalnum(*p) || *p == '.' ||
244 				*p == '-' || *p == ':'))
245 				p++;
246 			if (*p++ != ']')
247 				return NULL;
248 		} else {
249 			while ((isascii(*p) && isalnum(*p)) ||
250 			       strchr(".-_", *p))
251 				p++;
252 		}
253 		if (*p == ',' && p[1] == '@')
254 			p++;
255 		else if (*p == ':' && p[1] != '@')
256 			p++;
257 		else
258 			return NULL;
259 	}
260 
261 	s = p;
262 
263 	/* local-part */
264 	if (*p == '\"') {
265 		p++;
266 		while (*p && *p != '\"') {
267 			if (*p == '\\') {
268 				if (!*++p)
269 					return NULL;
270 			}
271 			p++;
272 		}
273 		if (!*p++)
274 			return NULL;
275 	} else {
276 		while (*p && *p != '@' && *p != '>') {
277 			if (*p == '\\') {
278 				if (!*++p)
279 					return NULL;
280 			} else {
281 			if (*p <= ' ' || (*p & 128) ||
282 			    strchr("<>()[]\\,;:\"", *p))
283 				return NULL;
284 			}
285 			p++;
286 		}
287 	}
288 
289 	/* @domain */
290 	if (*p == '@') {
291 		p++;
292 		if (*p == '[') {
293 			p++;
294 			while (isascii(*p) &&
295 			       (isalnum(*p) || *p == '.' ||
296 				*p == '-' || *p == ':'))
297 				p++;
298 			if (*p++ != ']')
299 				return NULL;
300 		} else {
301 			while ((isascii(*p) && isalnum(*p)) ||
302 			       strchr(".-_", *p))
303 				p++;
304 		}
305 	}
306 
307 	if (*p++ != '>')
308 		return NULL;
309 	if (*p && *p != ' ')
310 		return NULL;
311 	len = p - s - 1;
312 
313 	if (*s == '\0' || len <= 0)
314 	{
315 		s = MAILER_DAEMON;
316 		len = strlen(s);
317 	}
318 
319 	p = malloc(len + 1);
320 	if (p == NULL) {
321 		printf("421 4.3.0 memory exhausted\r\n");
322 		exit(EX_TEMPFAIL);
323 	}
324 
325 	strncpy(p, s, len);
326 	p[len] = '\0';
327 	return p;
328 }
329 
330 char *
331 process_recipient(addr)
332 	char *addr;
333 {
334 	if (getpwnam(addr) == NULL) {
335 		return "550 5.1.1 user unknown";
336 	}
337 
338 	return NULL;
339 }
340 
341 #define RCPT_GROW	30
342 
343 void
344 dolmtp(bouncequota)
345 	bool bouncequota;
346 {
347 	char *return_path = NULL;
348 	char **rcpt_addr = NULL;
349 	int rcpt_num = 0;
350 	int rcpt_alloc = 0;
351 	bool gotlhlo = FALSE;
352 	char myhostname[MAXHOSTNAMELEN];
353 	char buf[4096];
354 	char *err;
355 	char *p;
356 	int i;
357 
358 	gethostname(myhostname, sizeof myhostname - 1);
359 
360 	printf("220 %s LMTP ready\r\n", myhostname);
361 	for (;;) {
362 		if (sigterm_caught) {
363 			for (; rcpt_num > 0; rcpt_num--)
364 				printf("451 4.3.0 shutting down\r\n");
365 			exit(EX_OK);
366 		}
367 		fflush(stdout);
368 		if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
369 			exit(EX_OK);
370 		}
371 		p = buf + strlen(buf) - 1;
372 		if (p >= buf && *p == '\n')
373 			*p-- = '\0';
374 		if (p >= buf && *p == '\r')
375 			*p-- = '\0';
376 
377 		switch (buf[0]) {
378 
379 		case 'd':
380 		case 'D':
381 			if (strcasecmp(buf, "data") == 0) {
382 				if (rcpt_num == 0) {
383 					printf("503 5.5.1 No recipients\r\n");
384 					continue;
385 				}
386 				store(return_path, rcpt_num);
387 				if (bfd == -1 || hfd == -1)
388 					continue;
389 
390 				for (i = 0; i < rcpt_num; i++) {
391 					p = strchr(rcpt_addr[i], '+');
392 					if (p != NULL)
393 						*p++ = '\0';
394 					deliver(hfd, bfd, rcpt_addr[i],
395 						bouncequota);
396 				}
397 				close(bfd);
398 				close(hfd);
399 				goto rset;
400 			}
401 			goto syntaxerr;
402 			/* NOTREACHED */
403 			break;
404 
405 		case 'l':
406 		case 'L':
407 			if (strncasecmp(buf, "lhlo ", 5) == 0)
408 			{
409 				/* check for duplicate per RFC 1651 4.2 */
410 				if (gotlhlo)
411 				{
412 					printf("503 %s Duplicate LHLO\r\n",
413 					       myhostname);
414 					continue;
415 				}
416 				gotlhlo = TRUE;
417 				printf("250-%s\r\n", myhostname);
418 				if (EightBitMime)
419 					printf("250-8BITMIME\r\n");
420 				printf("250-ENHANCEDSTATUSCODES\r\n");
421 				printf("250 PIPELINING\r\n");
422 				continue;
423 			}
424 			goto syntaxerr;
425 			/* NOTREACHED */
426 			break;
427 
428 		case 'm':
429 		case 'M':
430 			if (strncasecmp(buf, "mail ", 5) == 0) {
431 				if (return_path != NULL) {
432 					printf("503 5.5.1 Nested MAIL command\r\n");
433 					continue;
434 				}
435 				if (strncasecmp(buf+5, "from:", 5) != 0 ||
436 				    ((return_path = parseaddr(buf+10)) == NULL)) {
437 					printf("501 5.5.4 Syntax error in parameters\r\n");
438 					continue;
439 				}
440 				printf("250 2.5.0 ok\r\n");
441 				continue;
442 			}
443 			goto syntaxerr;
444 
445 		case 'n':
446 		case 'N':
447 			if (strcasecmp(buf, "noop") == 0) {
448 				printf("250 2.0.0 ok\r\n");
449 				continue;
450 			}
451 			goto syntaxerr;
452 
453 		case 'q':
454 		case 'Q':
455 			if (strcasecmp(buf, "quit") == 0) {
456 				printf("221 2.0.0 bye\r\n");
457 				exit(EX_OK);
458 			}
459 			goto syntaxerr;
460 
461 		case 'r':
462 		case 'R':
463 			if (strncasecmp(buf, "rcpt ", 5) == 0) {
464 				if (return_path == NULL) {
465 					printf("503 5.5.1 Need MAIL command\r\n");
466 					continue;
467 				}
468 				if (rcpt_num >= rcpt_alloc) {
469 					rcpt_alloc += RCPT_GROW;
470 					rcpt_addr = (char **)
471 						realloc((char *)rcpt_addr,
472 							rcpt_alloc * sizeof(char **));
473 					if (rcpt_addr == NULL) {
474 						printf("421 4.3.0 memory exhausted\r\n");
475 						exit(EX_TEMPFAIL);
476 					}
477 				}
478 				if (strncasecmp(buf+5, "to:", 3) != 0 ||
479 				    ((rcpt_addr[rcpt_num] = parseaddr(buf+8)) == NULL)) {
480 					printf("501 5.5.4 Syntax error in parameters\r\n");
481 					continue;
482 				}
483 				if ((err = process_recipient(rcpt_addr[rcpt_num])) != NULL) {
484 					printf("%s\r\n", err);
485 					continue;
486 				}
487 				rcpt_num++;
488 				printf("250 2.1.5 ok\r\n");
489 				continue;
490 			}
491 			else if (strcasecmp(buf, "rset") == 0) {
492 				printf("250 2.0.0 ok\r\n");
493 
494   rset:
495 				while (rcpt_num > 0) {
496 					free(rcpt_addr[--rcpt_num]);
497 				}
498 				if (return_path != NULL)
499 					free(return_path);
500 				return_path = NULL;
501 				continue;
502 			}
503 			goto syntaxerr;
504 
505 		case 'v':
506 		case 'V':
507 			if (strncasecmp(buf, "vrfy ", 5) == 0) {
508 				printf("252 2.3.3 try RCPT to attempt delivery\r\n");
509 				continue;
510 			}
511 			goto syntaxerr;
512 
513 		default:
514   syntaxerr:
515 			printf("500 5.5.2 Syntax error\r\n");
516 			continue;
517 		}
518 	}
519 }
520 
521 static void
522 store(from, lmtprcpts)
523 	char *from;
524 	int lmtprcpts;
525 {
526 	FILE *fp = NULL;
527 	time_t tval;
528 	bool fullline = TRUE;	/* current line is terminated */
529 	bool prevfl;		/* previous line was terminated */
530 	char line[MAXLINE];
531 	FILE *bfp, *hfp;
532 	char *btn, *htn;
533 	int in_header_section;
534 	int newfd;
535 
536 	bfd = -1;
537 	hfd = -1;
538 	btn = strdup(_PATH_LOCTMP);
539 	if ((bfd = mkstemp(btn)) == -1 || (bfp = fdopen(bfd, "w+")) == NULL) {
540 		if (bfd != -1)
541 			(void) close(bfd);
542 		if (lmtprcpts) {
543 			printf("451 4.3.0 unable to open temporary file\r\n");
544 			return;
545 		} else {
546 			mailerr("451 4.3.0", "unable to open temporary file");
547 			exit(eval);
548 		}
549 	}
550 	(void) unlink(btn);
551 	free(btn);
552 
553 	if (lmtpmode) {
554 		printf("354 go ahead\r\n");
555 		fflush(stdout);
556 	}
557 
558 	htn = strdup(_PATH_LOCHTMP);
559 	if ((hfd = mkstemp(htn)) == -1 || (hfp = fdopen(hfd, "w+")) == NULL) {
560 		if (hfd != -1)
561 			(void) close(hfd);
562 		e_to_sys(errno);
563 		err("unable to open temporary file");
564 	}
565 	(void) unlink(htn);
566 	free(htn);
567 
568 	in_header_section = TRUE;
569 	content_length = 0;
570 	fp = hfp;
571 
572 	line[0] = '\0';
573 	while (fgets(line, sizeof(line), stdin) != (char *)NULL)
574 	{
575 		size_t line_len = 0;
576 		int peek;
577 
578 		prevfl = fullline;	/* preserve state of previous line */
579 		while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
580 			line_len++;
581 		line_len++;
582 
583 		/* Check for dot-stuffing */
584 		if (prevfl && lmtprcpts && line[0] == '.')
585 		{
586 			if (line[1] == '\n' ||
587 			    (line[1] == '\r' && line[2] == '\n'))
588 				goto lmtpdot;
589 			memcpy(line, line + 1, line_len);
590 			line_len--;
591 		}
592 
593 		/* Check to see if we have the full line from fgets() */
594 		fullline = FALSE;
595 		if (line_len > 0)
596 		{
597 			if (line[line_len - 1] == '\n')
598 			{
599 				if (line_len >= 2 &&
600 				    line[line_len - 2] == '\r')
601 				{
602 					line[line_len - 2] = '\n';
603 					line[line_len - 1] = '\0';
604 					line_len--;
605 				}
606 				fullline = TRUE;
607 			}
608 			else if (line[line_len - 1] == '\r')
609 			{
610 				/* Did we just miss the CRLF? */
611 				peek = fgetc(stdin);
612 				if (peek == '\n')
613 				{
614 					line[line_len - 1] = '\n';
615 					fullline = TRUE;
616 				}
617 				else
618 					(void) ungetc(peek, stdin);
619 			}
620 		}
621 		else
622 			fullline = TRUE;
623 
624 		if (prevfl && line[0] == '\n' && in_header_section) {
625 			in_header_section = FALSE;
626 			if (fflush(fp) == EOF || ferror(fp)) {
627 				if (lmtprcpts) {
628 					while (lmtprcpts--)
629 						printf("451 4.3.0 temporary file write error\r\n");
630 					fclose(fp);
631 					return;
632 				} else {
633 					mailerr("451 4.3.0",
634 						"temporary file write error");
635 					fclose(fp);
636 					exit(eval);
637 				}
638 			}
639 			fp = bfp;
640 			continue;
641 		}
642 
643 		if (in_header_section) {
644 			if (strncasecmp("Content-Length:", line, 15) == 0) {
645 				continue; /* skip this header */
646 			}
647 		} else
648 			content_length += strlen(line);
649 		(void) fwrite(line, sizeof(char), line_len, fp);
650 		if (ferror(fp)) {
651 			if (lmtprcpts) {
652 				while (lmtprcpts--)
653 					printf("451 4.3.0 temporary file write error\r\n");
654 				fclose(fp);
655 				return;
656 			} else {
657 				mailerr("451 4.3.0",
658 					"temporary file write error");
659 				fclose(fp);
660 				exit(eval);
661 			}
662 		}
663 	}
664 	if (sigterm_caught) {
665 		if (lmtprcpts)
666 			while (lmtprcpts--)
667 				printf("451 4.3.0 shutting down\r\n");
668 		else
669 			mailerr("451 4.3.0", "shutting down");
670 		fclose(fp);
671 		exit(eval);
672 	}
673 
674 	if (lmtprcpts) {
675 		/* Got a premature EOF -- toss message and exit */
676 		exit(EX_OK);
677 	}
678 
679 	/* If message not newline terminated, need an extra. */
680 	if (!strchr(line, '\n')) {
681 		(void) putc('\n', fp);
682 		content_length++;
683 	}
684 
685   lmtpdot:
686 
687 	/* Output a newline; note, empty messages are allowed. */
688 	(void) putc('\n', fp);
689 
690 	if (fflush(fp) == EOF || ferror(fp)) {
691 		if (lmtprcpts) {
692 			while (lmtprcpts--) {
693 				printf("451 4.3.0 temporary file write error\r\n");
694 			}
695 			fclose(fp);
696 			return;
697 		} else {
698 			mailerr("451 4.3.0", "temporary file write error");
699 			fclose(fp);
700 			exit(eval);
701 		}
702 	}
703 
704 	if ((newfd = dup(bfd)) >= 0) {
705 		fclose(bfp);
706 		bfd = newfd;
707 	}
708 	if ((newfd = dup(hfd)) >= 0) {
709 		fclose(hfp);
710 		hfd = newfd;
711 	}
712 	(void) time(&tval);
713 	(void) snprintf(unix_from_line, sizeof (unix_from_line), "From %s %s",
714 	    from, ctime(&tval));
715 	ulen = strlen(unix_from_line);
716 }
717 
718 static void
719 handle_error(err_num, bouncequota, path)
720 	int err_num;
721 	bool bouncequota;
722 	char *path;
723 {
724 #ifdef EDQUOT
725 	if (err_num == EDQUOT && bouncequota) {
726 		mailerr("552 5.2.2", "%s: %s", path, sm_errstring(err_num));
727 	} else
728 #endif /* EDQUOT */
729 		mailerr("450 4.2.0", "%s: %s", path, sm_errstring(err_num));
730 }
731 
732 static void
733 deliver(hfd, bfd, name, bouncequota)
734 	int hfd;
735 	int bfd;
736 	char *name;
737 	bool bouncequota;
738 {
739 	struct stat fsb, sb;
740 	int mbfd = -1, nr, nw = 0, off;
741 	char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
742 	off_t curoff, cursize;
743 	int len;
744 	struct passwd *pw = NULL;
745 
746 	/*
747  	* Disallow delivery to unknown names -- special mailboxes
748  	* can be handled in the sendmail aliases file.
749  	*/
750 	if ((pw = getpwnam(name)) == NULL) {
751 		eval = EX_TEMPFAIL;
752 		mailerr("451 4.3.0", "cannot lookup name: %s", name);
753 		return;
754 	}
755 	endpwent();
756 
757 	if (sigterm_caught) {
758 		mailerr("451 4.3.0", "shutting down");
759 		return;
760 	}
761 
762 	/* mailbox may be NFS mounted, seteuid to user */
763 	targ_uid = pw->pw_uid;
764 	(void) seteuid(targ_uid);
765 
766 	if ((saved_uid != 0) && (src_uid != targ_uid)) {
767 		/*
768 		 * If saved_uid == 0 (root), anything is OK; this is
769 		 * as it should be.  But to prevent a random user from
770 		 * calling "mail.local foo" in an attempt to hijack
771 		 * foo's mail-box, make sure src_uid == targ_uid o/w.
772 		 */
773 		warn("%s: wrong owner (is %d, should be %d)",
774 			name, src_uid, targ_uid);
775 		eval = EX_CANTCREAT;
776 		return;
777 	}
778 
779 	path[0] = '\0';
780 	(void) snprintf(path, sizeof (path), "%s/%s", _PATH_MAILDIR, name);
781 
782 	/*
783 	 * If the mailbox is linked or a symlink, fail.  There's an obvious
784 	 * race here, that the file was replaced with a symbolic link after
785 	 * the lstat returned, but before the open.  We attempt to detect
786 	 * this by comparing the original stat information and information
787 	 * returned by an fstat of the file descriptor returned by the open.
788 	 *
789 	 * NB: this is a symptom of a larger problem, that the mail spooling
790 	 * directory is writeable by the wrong users.  If that directory is
791 	 * writeable, system security is compromised for other reasons, and
792 	 * it cannot be fixed here.
793 	 *
794 	 * If we created the mailbox, set the owner/group.  If that fails,
795 	 * just return.  Another process may have already opened it, so we
796 	 * can't unlink it.  Historically, binmail set the owner/group at
797 	 * each mail delivery.  We no longer do this, assuming that if the
798 	 * ownership or permissions were changed there was a reason.
799 	 *
800 	 * XXX
801 	 * open(2) should support flock'ing the file.
802 	 */
803 tryagain:
804 	/* should check lock status, but... maillock return no value */
805 	maillock(name, 10);
806 
807 	if (sigterm_caught) {
808 		mailerr("451 4.3.0", "shutting down");
809 		goto err0;
810 	}
811 
812 	if (lstat(path, &sb)) {
813 		mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY,
814 				S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
815 		if (mbfd != -1)
816 			(void) fchmod(mbfd, 0660);
817 
818 
819 		if (mbfd == -1) {
820 			if (errno == EEXIST) {
821 				mailunlock();
822 				goto tryagain;
823 			}
824 		}
825 	} else if (sb.st_nlink != 1) {
826 		mailerr("550 5.2.0", "%s: too many links", path);
827 		goto err0;
828 	} else if (!S_ISREG(sb.st_mode)) {
829 		mailerr("550 5.2.0", "%s: irregular file", path);
830 		goto err0;
831 	} else {
832 		mbfd = open(path, O_APPEND|O_WRONLY, 0);
833 		if (mbfd != -1 &&
834 		    (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
835 		    S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
836 		    sb.st_ino != fsb.st_ino)) {
837 			eval = EX_TEMPFAIL;
838 			mailerr("550 5.2.0",
839 				"%s: fstat: file changed after open", path);
840 			goto err1;
841 		}
842 	}
843 
844 	if (mbfd == -1) {
845 		mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
846 		goto err0;
847 	}
848 
849 	if (sigterm_caught) {
850 		mailerr("451 4.3.0", "shutting down");
851 		goto err0;
852 	}
853 
854 	/* Get the starting offset of the new message for biff. */
855 	curoff = lseek(mbfd, (off_t)0, SEEK_END);
856 	(void) snprintf(biffmsg, sizeof (biffmsg), "%s@%ld\n", name, curoff);
857 
858 	/* Copy the message into the file. */
859 	if (lseek(hfd, (off_t)0, SEEK_SET) == (off_t)-1) {
860 		mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
861 		goto err1;
862 	}
863 	/* Copy the message into the file. */
864 	if (lseek(bfd, (off_t)0, SEEK_SET) == (off_t)-1) {
865 		mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
866 		goto err1;
867 	}
868 	if ((write(mbfd, unix_from_line, ulen)) != ulen) {
869 		handle_error(errno, bouncequota, path);
870 		goto err2;
871 	}
872 
873 	if (sigterm_caught) {
874 		mailerr("451 4.3.0", "shutting down");
875 		goto err2;
876 	}
877 
878 	while ((nr = read(hfd, buf, sizeof (buf))) > 0)
879 		for (off = 0; off < nr; nr -= nw, off += nw)
880 			if ((nw = write(mbfd, buf + off, nr)) < 0)
881 			{
882 				handle_error(errno, bouncequota, path);
883 				goto err2;
884 			}
885 	if (nr < 0) {
886 		handle_error(errno, bouncequota, path);
887 		goto err2;
888 	}
889 
890 	if (sigterm_caught) {
891 		mailerr("451 4.3.0", "shutting down");
892 		goto err2;
893 	}
894 
895 	(void) snprintf(buf, sizeof (buf), "Content-Length: %d\n\n",
896 	    content_length);
897 	len = strlen(buf);
898 	if (write(mbfd, buf, len) != len) {
899 		handle_error(errno, bouncequota, path);
900 		goto err2;
901 	}
902 
903 	if (sigterm_caught) {
904 		mailerr("451 4.3.0", "shutting down");
905 		goto err2;
906 	}
907 
908 	while ((nr = read(bfd, buf, sizeof (buf))) > 0) {
909 		for (off = 0; off < nr; nr -= nw, off += nw)
910 			if ((nw = write(mbfd, buf + off, nr)) < 0) {
911 				handle_error(errno, bouncequota, path);
912 				goto err2;
913 			}
914 		if (sigterm_caught) {
915 			mailerr("451 4.3.0", "shutting down");
916 			goto err2;
917 		}
918 	}
919 	if (nr < 0) {
920 		handle_error(errno, bouncequota, path);
921 		goto err2;
922 	}
923 
924 	/* Flush to disk, don't wait for update. */
925 	if (fsync(mbfd)) {
926 		handle_error(errno, bouncequota, path);
927 err2:		if (mbfd >= 0)
928 			(void)ftruncate(mbfd, curoff);
929 err1:		(void)close(mbfd);
930 err0:		mailunlock();
931 		(void)seteuid(saved_uid);
932 		return;
933 	}
934 
935 	/*
936 	**  Save the current size so if the close() fails below
937 	**  we can make sure no other process has changed the mailbox
938 	**  between the failed close and the re-open()/re-lock().
939 	**  If something else has changed the size, we shouldn't
940 	**  try to truncate it as we may do more harm then good
941 	**  (e.g., truncate a later message delivery).
942 	*/
943 
944 	if (fstat(mbfd, &sb) < 0)
945 		cursize = 0;
946 	else
947 		cursize = sb.st_size;
948 
949 	/* Close and check -- NFS doesn't write until the close. */
950 	if (close(mbfd))
951 	{
952 		handle_error(errno, bouncequota, path);
953 		mbfd = open(path, O_WRONLY, 0);
954 		if (mbfd < 0 ||
955 		    cursize == 0
956 		    || flock(mbfd, LOCK_EX) < 0 ||
957 		    fstat(mbfd, &sb) < 0 ||
958 		    sb.st_size != cursize ||
959 		    sb.st_nlink != 1 ||
960 		    !S_ISREG(sb.st_mode) ||
961 		    sb.st_dev != fsb.st_dev ||
962 		    sb.st_ino != fsb.st_ino ||
963 		    sb.st_uid != fsb.st_uid)
964 		{
965 			/* Don't use a bogus file */
966 			if (mbfd >= 0)
967 			{
968 				(void) close(mbfd);
969 				mbfd = -1;
970 			}
971 		}
972 
973 		/* Attempt to truncate back to pre-write size */
974 		goto err2;
975 	} else
976 		notifybiff(biffmsg);
977 
978 	mailunlock();
979 
980 	(void)seteuid(saved_uid);
981 
982 	if (lmtpmode) {
983 		printf("250 2.1.5 %s OK\r\n", name);
984 	}
985 }
986 
987 static void
988 notifybiff(msg)
989 	char *msg;
990 {
991 	static struct sockaddr_in addr;
992 	static int f = -1;
993 	struct hostent *hp;
994 	struct servent *sp;
995 	int len;
996 
997 	if (msg == NULL) {
998 		/* Be silent if biff service not available. */
999 		if ((sp = getservbyname("biff", "udp")) == NULL)
1000 			return;
1001 		if ((hp = gethostbyname("localhost")) == NULL) {
1002 			warn("localhost: %s", strerror(errno));
1003 			return;
1004 		}
1005 		addr.sin_family = hp->h_addrtype;
1006 		(void) memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
1007 		addr.sin_port = sp->s_port;
1008 		return;
1009 	}
1010 
1011 	if (addr.sin_family == 0)
1012 		return; /* did not initialize */
1013 
1014 	if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
1015 		warn("socket: %s", strerror(errno));
1016 		return;
1017 	}
1018 	len = strlen(msg) + 1;
1019 	if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof (addr))
1020 	    != len)
1021 		warn("sendto biff: %s", strerror(errno));
1022 }
1023 
1024 static void
1025 usage()
1026 {
1027 	eval = EX_USAGE;
1028 	err("usage: mail.local [-l] [-f from] user ...");
1029 }
1030 
1031 static void
1032 /*VARARGS2*/
1033 #ifdef __STDC__
1034 mailerr(const char *hdr, const char *fmt, ...)
1035 #else
1036 mailerr(hdr, fmt, va_alist)
1037 	const char *hdr;
1038 	const char *fmt;
1039 	va_dcl
1040 #endif
1041 {
1042 	va_list ap;
1043 
1044 #ifdef __STDC__
1045 	va_start(ap, fmt);
1046 #else
1047 	va_start(ap);
1048 #endif
1049 	if (lmtpmode)
1050 	{
1051 		if (hdr != NULL)
1052 			printf("%s ", hdr);
1053 		vprintf(fmt, ap);
1054 		printf("\r\n");
1055 	}
1056 	else
1057 	{
1058 		e_to_sys(errno);
1059 		vwarn(fmt, ap);
1060 	}
1061 }
1062 
1063 static void
1064 /*VARARGS1*/
1065 #ifdef __STDC__
1066 err(const char *fmt, ...)
1067 #else
1068 err(fmt, va_alist)
1069 	const char *fmt;
1070 	va_dcl
1071 #endif
1072 {
1073 	va_list ap;
1074 
1075 #ifdef __STDC__
1076 	va_start(ap, fmt);
1077 #else
1078 	va_start(ap);
1079 #endif
1080 	vwarn(fmt, ap);
1081 	va_end(ap);
1082 
1083 	exit(eval);
1084 }
1085 
1086 static void
1087 /*VARARGS1*/
1088 #ifdef __STDC__
1089 warn(const char *fmt, ...)
1090 #else
1091 warn(fmt, va_alist)
1092 	const char *fmt;
1093 	va_dcl
1094 #endif
1095 {
1096 	va_list ap;
1097 
1098 #ifdef __STDC__
1099 	va_start(ap, fmt);
1100 #else
1101 	va_start(ap);
1102 #endif
1103 	vwarn(fmt, ap);
1104 	va_end(ap);
1105 }
1106 
1107 static void
1108 vwarn(fmt, ap)
1109 	const char *fmt;
1110 	va_list ap;
1111 {
1112 	/*
1113 	 * Log the message to stderr.
1114 	 *
1115 	 * Don't use LOG_PERROR as an openlog() flag to do this,
1116 	 * it's not portable enough.
1117 	 */
1118 	if (eval != EX_USAGE)
1119 		(void) fprintf(stderr, "mail.local: ");
1120 	(void) vfprintf(stderr, fmt, ap);
1121 	(void) fprintf(stderr, "\n");
1122 
1123 	/* Log the message to syslog. */
1124 	vsyslog(LOG_ERR, fmt, ap);
1125 }
1126 
1127 /*
1128  * e_to_sys --
1129  *	Guess which errno's are temporary.  Gag me.
1130  */
1131 static void
1132 e_to_sys(num)
1133 	int num;
1134 {
1135 	/* Temporary failures override hard errors. */
1136 	if (eval == EX_TEMPFAIL)
1137 		return;
1138 
1139 	switch (num)		/* Hopefully temporary errors. */
1140 	{
1141 #ifdef EDQUOT
1142 	case EDQUOT:		/* Disc quota exceeded */
1143 		if (bouncequota)
1144 		{
1145 			eval = EX_UNAVAILABLE;
1146 			break;
1147 		}
1148 #endif /* EDQUOT */
1149 #ifdef EAGAIN
1150 		/* FALLTHROUGH */
1151 	case EAGAIN:		/* Resource temporarily unavailable */
1152 #endif
1153 #ifdef EBUSY
1154 	case EBUSY:		/* Device busy */
1155 #endif
1156 #ifdef EPROCLIM
1157 	case EPROCLIM:		/* Too many processes */
1158 #endif
1159 #ifdef EUSERS
1160 	case EUSERS:		/* Too many users */
1161 #endif
1162 #ifdef ECONNABORTED
1163 	case ECONNABORTED:	/* Software caused connection abort */
1164 #endif
1165 #ifdef ECONNREFUSED
1166 	case ECONNREFUSED:	/* Connection refused */
1167 #endif
1168 #ifdef ECONNRESET
1169 	case ECONNRESET:	/* Connection reset by peer */
1170 #endif
1171 #ifdef EDEADLK
1172 	case EDEADLK:		/* Resource deadlock avoided */
1173 #endif
1174 #ifdef EFBIG
1175 	case EFBIG:		/* File too large */
1176 #endif
1177 #ifdef EHOSTDOWN
1178 	case EHOSTDOWN:		/* Host is down */
1179 #endif
1180 #ifdef EHOSTUNREACH
1181 	case EHOSTUNREACH:	/* No route to host */
1182 #endif
1183 #ifdef EMFILE
1184 	case EMFILE:		/* Too many open files */
1185 #endif
1186 #ifdef ENETDOWN
1187 	case ENETDOWN:		/* Network is down */
1188 #endif
1189 #ifdef ENETRESET
1190 	case ENETRESET:		/* Network dropped connection on reset */
1191 #endif
1192 #ifdef ENETUNREACH
1193 	case ENETUNREACH:	/* Network is unreachable */
1194 #endif
1195 #ifdef ENFILE
1196 	case ENFILE:		/* Too many open files in system */
1197 #endif
1198 #ifdef ENOBUFS
1199 	case ENOBUFS:		/* No buffer space available */
1200 #endif
1201 #ifdef ENOMEM
1202 	case ENOMEM:		/* Cannot allocate memory */
1203 #endif
1204 #ifdef ENOSPC
1205 	case ENOSPC:		/* No space left on device */
1206 #endif
1207 #ifdef EROFS
1208 	case EROFS:		/* Read-only file system */
1209 #endif
1210 #ifdef ESTALE
1211 	case ESTALE:		/* Stale NFS file handle */
1212 #endif
1213 #ifdef ETIMEDOUT
1214 	case ETIMEDOUT:		/* Connection timed out */
1215 #endif
1216 #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
1217 	case EWOULDBLOCK:	/* Operation would block. */
1218 #endif
1219 		eval = EX_TEMPFAIL;
1220 		break;
1221 	default:
1222 		eval = EX_UNAVAILABLE;
1223 		break;
1224 	}
1225 }
1226