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