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
17static 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
25static char sccsid[] = "@(#)mail.local.c	8.83 (Berkeley) 12/17/98";
26static 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
85typedef int bool;
86
87#define	FALSE	0
88#define	TRUE	1
89
90bool	EightBitMime = TRUE;		/* advertise 8BITMIME in LMTP */
91static int eval = EX_OK;			/* sysexits.h error value. */
92static int lmtpmode = 0;
93bool	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
102static void	deliver(int, int, char *, bool);
103static void	e_to_sys(int);
104static void	err(const char *fmt, ...);
105static void	notifybiff(char *);
106static void	store(char *, int);
107static void	usage(void);
108static void	vwarn();
109static void	warn(const char *fmt, ...);
110static void	mailerr(const char *, const char *, ...);
111static void	sigterm_handler();
112
113static char	unix_from_line[MAXLINE];
114static int	ulen;
115static int	content_length;
116static int	bfd, hfd; /* temp file */
117static uid_t	src_uid, targ_uid, saved_uid;
118static int	sigterm_caught;
119
120int
121main(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
218void
219sigterm_handler()
220{
221	sigterm_caught = TRUE;
222	(void) sigignore(SIGTERM);
223}
224
225char *
226parseaddr(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
330char *
331process_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
343void
344dolmtp(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
521static void
522store(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
718static void
719handle_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
732static void
733deliver(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	 */
803tryagain:
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);
927err2:		if (mbfd >= 0)
928			(void)ftruncate(mbfd, curoff);
929err1:		(void)close(mbfd);
930err0:		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
987static void
988notifybiff(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
1024static void
1025usage()
1026{
1027	eval = EX_USAGE;
1028	err("usage: mail.local [-l] [-f from] user ...");
1029}
1030
1031static void
1032/*VARARGS2*/
1033#ifdef __STDC__
1034mailerr(const char *hdr, const char *fmt, ...)
1035#else
1036mailerr(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
1063static void
1064/*VARARGS1*/
1065#ifdef __STDC__
1066err(const char *fmt, ...)
1067#else
1068err(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
1086static void
1087/*VARARGS1*/
1088#ifdef __STDC__
1089warn(const char *fmt, ...)
1090#else
1091warn(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
1107static void
1108vwarn(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 */
1131static void
1132e_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