xref: /openbsd-src/libexec/mail.local/mail.local.c (revision 94c8de549078a156f9fd3b5860c3a954e0098074)
1*94c8de54Sflorian /*	$OpenBSD: mail.local.c,v 1.43 2024/05/09 08:35:03 florian Exp $	*/
209850d41Smillert 
3df930be7Sderaadt /*-
409850d41Smillert  * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com>
509850d41Smillert  * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu>
6df930be7Sderaadt  * Copyright (c) 1990 The Regents of the University of California.
7df930be7Sderaadt  * All rights reserved.
8df930be7Sderaadt  *
9df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
10df930be7Sderaadt  * modification, are permitted provided that the following conditions
11df930be7Sderaadt  * are met:
12df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
13df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
14df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
15df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
16df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
17e33d3bd3Smillert  * 3. Neither the name of the University nor the names of its contributors
18df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
19df930be7Sderaadt  *    without specific prior written permission.
20df930be7Sderaadt  *
21df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31df930be7Sderaadt  * SUCH DAMAGE.
32df930be7Sderaadt  */
33df930be7Sderaadt 
34b9fc9a72Sderaadt #include <sys/types.h>
35df930be7Sderaadt #include <sys/stat.h>
36df930be7Sderaadt #include <sys/socket.h>
37c711e483Smillert #include <sys/wait.h>
38df930be7Sderaadt #include <netinet/in.h>
39e1869fb3Smillert #include <sysexits.h>
40df930be7Sderaadt #include <syslog.h>
41df930be7Sderaadt #include <fcntl.h>
42df930be7Sderaadt #include <netdb.h>
43df930be7Sderaadt #include <pwd.h>
44df930be7Sderaadt #include <time.h>
45df930be7Sderaadt #include <unistd.h>
46b9fc9a72Sderaadt #include <limits.h>
47df930be7Sderaadt #include <errno.h>
48df930be7Sderaadt #include <stdio.h>
49df930be7Sderaadt #include <stdlib.h>
50df930be7Sderaadt #include <string.h>
51c711e483Smillert #include <signal.h>
52df930be7Sderaadt #include "pathnames.h"
5309850d41Smillert #include "mail.local.h"
54df930be7Sderaadt 
55fbb93235Sderaadt int
main(int argc,char * argv[])565b48e80aSderaadt main(int argc, char *argv[])
57df930be7Sderaadt {
58df930be7Sderaadt 	struct passwd *pw;
598bc1a323Smillert 	int ch, fd, eval, lockfile=1;
60df930be7Sderaadt 	uid_t uid;
61df930be7Sderaadt 	char *from;
62df930be7Sderaadt 
63df930be7Sderaadt 	openlog("mail.local", LOG_PERROR, LOG_MAIL);
64df930be7Sderaadt 
65df930be7Sderaadt 	from = NULL;
668bc1a323Smillert 	while ((ch = getopt(argc, argv, "lLdf:r:")) != -1)
67df930be7Sderaadt 		switch (ch) {
68df930be7Sderaadt 		case 'd':		/* backward compatible */
69df930be7Sderaadt 			break;
70df930be7Sderaadt 		case 'f':
71df930be7Sderaadt 		case 'r':		/* backward compatible */
72df930be7Sderaadt 			if (from)
73e1869fb3Smillert 				merr(EX_USAGE, "multiple -f options");
74df930be7Sderaadt 			from = optarg;
75df930be7Sderaadt 			break;
76df930be7Sderaadt 		case 'l':
77d48bae1cSdm 			lockfile=1;
78d48bae1cSdm 			break;
79d48bae1cSdm 		case 'L':
80d48bae1cSdm 			lockfile=0;
81df930be7Sderaadt 			break;
82df930be7Sderaadt 		default:
83df930be7Sderaadt 			usage();
84df930be7Sderaadt 		}
85df930be7Sderaadt 	argc -= optind;
86df930be7Sderaadt 	argv += optind;
87df930be7Sderaadt 
8809850d41Smillert 	if (!*argv)
89df930be7Sderaadt 		usage();
9034e47be8Sderaadt 
91df930be7Sderaadt 	/*
92df930be7Sderaadt 	 * If from not specified, use the name from getlogin() if the
93df930be7Sderaadt 	 * uid matches, otherwise, use the name from the password file
94df930be7Sderaadt 	 * corresponding to the uid.
95df930be7Sderaadt 	 */
96df930be7Sderaadt 	uid = getuid();
97df930be7Sderaadt 	if (!from && (!(from = getlogin()) ||
98df930be7Sderaadt 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
99df930be7Sderaadt 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
100df930be7Sderaadt 
101668b2622Sderaadt 	fd = storemail(from);
102e1869fb3Smillert 	for (eval = 0; *argv; ++argv) {
103e1869fb3Smillert 		if ((ch = deliver(fd, *argv, lockfile)) != 0)
104e1869fb3Smillert 			eval = ch;
105e1869fb3Smillert 	}
106df930be7Sderaadt 	exit(eval);
107df930be7Sderaadt }
108df930be7Sderaadt 
10914a9ceabSderaadt int
storemail(char * from)110668b2622Sderaadt storemail(char *from)
111df930be7Sderaadt {
112cc77d91aSderaadt 	FILE *fp = NULL;
113df930be7Sderaadt 	time_t tval;
11401f59d78Sop 	int fd, eline = 1;
115*94c8de54Sflorian 	char *tbuf, *line = NULL, *cnow;
11601f59d78Sop 	size_t linesize = 0;
11701f59d78Sop 	ssize_t linelen;
118df930be7Sderaadt 
119b8150c5dSmillert 	if ((tbuf = strdup(_PATH_LOCTMP)) == NULL)
120e1869fb3Smillert 		merr(EX_OSERR, "unable to allocate memory");
121b8150c5dSmillert 	if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+")))
122e1869fb3Smillert 		merr(EX_OSERR, "unable to open temporary file");
123b8150c5dSmillert 	(void)unlink(tbuf);
124b8150c5dSmillert 	free(tbuf);
125df930be7Sderaadt 
126df930be7Sderaadt 	(void)time(&tval);
127*94c8de54Sflorian 	cnow = ctime(&tval);
128*94c8de54Sflorian 	(void)fprintf(fp, "From %s %s", from, cnow ? cnow : "?\n");
129df930be7Sderaadt 
13001f59d78Sop 	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
13101f59d78Sop 		if (line[linelen - 1] == '\n')
13201f59d78Sop 			line[linelen - 1] = '\0';
133b8150c5dSmillert 		if (line[0] == '\0')
134df930be7Sderaadt 			eline = 1;
135df930be7Sderaadt 		else {
13601f59d78Sop 			if (eline && !strncmp(line, "From ", 5))
137df930be7Sderaadt 				(void)putc('>', fp);
138df930be7Sderaadt 			eline = 0;
139df930be7Sderaadt 		}
140b8150c5dSmillert 		(void)fprintf(fp, "%s\n", line);
141df930be7Sderaadt 		if (ferror(fp))
142df930be7Sderaadt 			break;
143df930be7Sderaadt 	}
14401f59d78Sop 	free(line);
145df930be7Sderaadt 
146df930be7Sderaadt 	/* Output a newline; note, empty messages are allowed. */
147df930be7Sderaadt 	(void)putc('\n', fp);
148df930be7Sderaadt 	(void)fflush(fp);
149df930be7Sderaadt 	if (ferror(fp))
150e1869fb3Smillert 		merr(EX_OSERR, "temporary file write error");
151df930be7Sderaadt 	return(fd);
152df930be7Sderaadt }
153df930be7Sderaadt 
15434e47be8Sderaadt int
deliver(int fd,char * name,int lockfile)1555b48e80aSderaadt deliver(int fd, char *name, int lockfile)
15634e47be8Sderaadt {
15734e47be8Sderaadt 	struct stat sb, fsb;
15834e47be8Sderaadt 	struct passwd *pw;
159e1869fb3Smillert 	int mbfd=-1, lfd=-1, rval=EX_OSERR;
160b9fc9a72Sderaadt 	char biffmsg[100], buf[8*1024], path[PATH_MAX];
16134e47be8Sderaadt 	off_t curoff;
162668b2622Sderaadt 	size_t off;
163668b2622Sderaadt 	ssize_t nr, nw;
16434e47be8Sderaadt 
16534e47be8Sderaadt 	/*
16634e47be8Sderaadt 	 * Disallow delivery to unknown names -- special mailboxes can be
16734e47be8Sderaadt 	 * handled in the sendmail aliases file.
16834e47be8Sderaadt 	 */
16934e47be8Sderaadt 	if (!(pw = getpwnam(name))) {
170e1869fb3Smillert 		mwarn("unknown name: %s", name);
171e1869fb3Smillert 		return(EX_NOUSER);
172df930be7Sderaadt 	}
17334e47be8Sderaadt 
17434e47be8Sderaadt 	(void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name);
17534e47be8Sderaadt 
17634e47be8Sderaadt 	if (lockfile) {
177c711e483Smillert 		lfd = lockspool(name, pw);
17834e47be8Sderaadt 		if (lfd == -1)
179e1869fb3Smillert 			return(EX_OSERR);
180fbb93235Sderaadt 	}
181df930be7Sderaadt 
18214a9ceabSderaadt 	/* after this point, always exit via bad to remove lockfile */
18314a9ceabSderaadt retry:
18414a9ceabSderaadt 	if (lstat(path, &sb)) {
18514a9ceabSderaadt 		if (errno != ENOENT) {
186e1869fb3Smillert 			mwarn("%s: %s", path, strerror(errno));
18714a9ceabSderaadt 			goto bad;
18814a9ceabSderaadt 		}
189a8085b99Sdm 		if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK,
190df69c215Sderaadt 		    S_IRUSR|S_IWUSR)) == -1) {
19114a9ceabSderaadt 			if (errno == EEXIST) {
19214a9ceabSderaadt 				/* file appeared since lstat */
19314a9ceabSderaadt 				goto retry;
19414a9ceabSderaadt 			} else {
195e1869fb3Smillert 				mwarn("%s: %s", path, strerror(errno));
196e1869fb3Smillert 				rval = EX_CANTCREAT;
19714a9ceabSderaadt 				goto bad;
19814a9ceabSderaadt 			}
19914a9ceabSderaadt 		}
20014a9ceabSderaadt 		/*
20114a9ceabSderaadt 		 * Set the owner and group.  Historically, binmail repeated
20214a9ceabSderaadt 		 * this at each mail delivery.  We no longer do this, assuming
20314a9ceabSderaadt 		 * that if the ownership or permissions were changed there
20414a9ceabSderaadt 		 * was a reason for doing so.
20514a9ceabSderaadt 		 */
206df69c215Sderaadt 		if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) {
207e1869fb3Smillert 			mwarn("chown %u:%u: %s", pw->pw_uid, pw->pw_gid, name);
20814a9ceabSderaadt 			goto bad;
20914a9ceabSderaadt 		}
21014a9ceabSderaadt 	} else {
21129624f1eSdm 		if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) {
212e1869fb3Smillert 			mwarn("%s: linked or special file", path);
21314a9ceabSderaadt 			goto bad;
214df930be7Sderaadt 		}
215df930be7Sderaadt 		if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK,
216df69c215Sderaadt 		    S_IRUSR|S_IWUSR)) == -1) {
217e1869fb3Smillert 			mwarn("%s: %s", path, strerror(errno));
21814a9ceabSderaadt 			goto bad;
21914a9ceabSderaadt 		}
220df69c215Sderaadt 		if (fstat(mbfd, &fsb) == -1) {
22114a9ceabSderaadt 			/* relating error to path may be bad style */
222e1869fb3Smillert 			mwarn("%s: %s", path, strerror(errno));
22314a9ceabSderaadt 			goto bad;
22414a9ceabSderaadt 		}
22514a9ceabSderaadt 		if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) {
226e1869fb3Smillert 			mwarn("%s: changed after open", path);
22714a9ceabSderaadt 			goto bad;
22814a9ceabSderaadt 		}
22914a9ceabSderaadt 		/* paranoia? */
23029624f1eSdm 		if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) {
231e1869fb3Smillert 			mwarn("%s: linked or special file", path);
232e1869fb3Smillert 			rval = EX_CANTCREAT;
23314a9ceabSderaadt 			goto bad;
234df930be7Sderaadt 		}
235df930be7Sderaadt 	}
236df930be7Sderaadt 
237df930be7Sderaadt 	curoff = lseek(mbfd, 0, SEEK_END);
238ad520ff8Sop 	(void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name,
239320e1832Sop 	    (long long)curoff);
240df930be7Sderaadt 	if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
241e1869fb3Smillert 		mwarn("temporary file: %s", strerror(errno));
242df930be7Sderaadt 		goto bad;
243df930be7Sderaadt 	}
244df930be7Sderaadt 
245df930be7Sderaadt 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
246df930be7Sderaadt 		for (off = 0; off < nr;  off += nw)
247df69c215Sderaadt 			if ((nw = write(mbfd, buf + off, nr - off)) == -1) {
248e1869fb3Smillert 				mwarn("%s: %s", path, strerror(errno));
24914a9ceabSderaadt 				(void)ftruncate(mbfd, curoff);
25014a9ceabSderaadt 				goto bad;
251df930be7Sderaadt 			}
252df930be7Sderaadt 
25314a9ceabSderaadt 	if (nr == 0) {
25414a9ceabSderaadt 		rval = 0;
25514a9ceabSderaadt 	} else {
25614a9ceabSderaadt 		(void)ftruncate(mbfd, curoff);
257e1869fb3Smillert 		mwarn("temporary file: %s", strerror(errno));
25814a9ceabSderaadt 	}
25914a9ceabSderaadt 
260df930be7Sderaadt bad:
261c711e483Smillert 	if (lfd != -1)
262c711e483Smillert 		unlockspool();
263df930be7Sderaadt 
26414a9ceabSderaadt 	if (mbfd != -1) {
265df930be7Sderaadt 		(void)fsync(mbfd);		/* Don't wait for update. */
266df930be7Sderaadt 		(void)close(mbfd);		/* Implicit unlock. */
26714a9ceabSderaadt 	}
268df930be7Sderaadt 
269df930be7Sderaadt 	if (!rval)
270df930be7Sderaadt 		notifybiff(biffmsg);
271df930be7Sderaadt 	return(rval);
272df930be7Sderaadt }
273df930be7Sderaadt 
274df930be7Sderaadt void
notifybiff(char * msg)2755b48e80aSderaadt notifybiff(char *msg)
276df930be7Sderaadt {
27723b882a1Smillert 	static struct addrinfo *res0;
27823b882a1Smillert 	struct addrinfo hints, *res;
279df930be7Sderaadt 	static int f = -1;
280668b2622Sderaadt 	size_t len;
28123b882a1Smillert 	int error;
282df930be7Sderaadt 
28323b882a1Smillert 	if (res0 == NULL) {
28423b882a1Smillert 		memset(&hints, 0, sizeof(hints));
28523b882a1Smillert 		hints.ai_family = PF_UNSPEC;
28623b882a1Smillert 		hints.ai_socktype = SOCK_DGRAM;
28723b882a1Smillert 
28823b882a1Smillert 		error = getaddrinfo("localhost", "biff", &hints, &res0);
28923b882a1Smillert 		if (error) {
290df930be7Sderaadt 			/* Be silent if biff service not available. */
29123b882a1Smillert 			if (error != EAI_SERVICE) {
292e1869fb3Smillert 				mwarn("localhost: %s", gai_strerror(error));
29323b882a1Smillert 			}
294df930be7Sderaadt 			return;
295df930be7Sderaadt 		}
296df930be7Sderaadt 	}
29723b882a1Smillert 
29823b882a1Smillert 	if (f == -1) {
29923b882a1Smillert 		for (res = res0; res != NULL; res = res->ai_next) {
30023b882a1Smillert 			f = socket(res->ai_family, res->ai_socktype,
30123b882a1Smillert 			    res->ai_protocol);
30223b882a1Smillert 			if (f != -1)
30323b882a1Smillert 				break;
30423b882a1Smillert 		}
30523b882a1Smillert 	}
30623b882a1Smillert 	if (f == -1) {
307e1869fb3Smillert 		mwarn("socket: %s", strerror(errno));
308df930be7Sderaadt 		return;
309df930be7Sderaadt 	}
31023b882a1Smillert 
31123b882a1Smillert 	len = strlen(msg) + 1;	/* XXX */
31223b882a1Smillert 	if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len)
313e1869fb3Smillert 		mwarn("sendto biff: %s", strerror(errno));
314df930be7Sderaadt }
315df930be7Sderaadt 
316c711e483Smillert static int lockfd = -1;
317c711e483Smillert static pid_t lockpid = -1;
318c711e483Smillert 
319c711e483Smillert int
lockspool(const char * name,struct passwd * pw)320c711e483Smillert lockspool(const char *name, struct passwd *pw)
321c711e483Smillert {
322c711e483Smillert 	int pfd[2];
323c711e483Smillert 	char ch;
324c711e483Smillert 
325c711e483Smillert 	if (geteuid() == 0)
326c711e483Smillert 		return getlock(name, pw);
327c711e483Smillert 
328c711e483Smillert 	/* If not privileged, open pipe to lockspool(1) instead */
329c711e483Smillert 	if (pipe2(pfd, O_CLOEXEC) == -1) {
330e1869fb3Smillert 		merr(EX_OSERR, "pipe: %s", strerror(errno));
331c711e483Smillert 		return -1;
332c711e483Smillert 	}
333c711e483Smillert 
334c711e483Smillert 	signal(SIGPIPE, SIG_IGN);
335c711e483Smillert 	switch ((lockpid = fork())) {
336c711e483Smillert 	case -1:
337e1869fb3Smillert 		merr(EX_OSERR, "fork: %s", strerror(errno));
338c711e483Smillert 		return -1;
339c711e483Smillert 	case 0:
340c711e483Smillert 		/* child */
341c711e483Smillert 		close(pfd[0]);
342c711e483Smillert 		dup2(pfd[1], STDOUT_FILENO);
343c711e483Smillert 		execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL);
344e1869fb3Smillert 		merr(EX_OSERR, "execl: lockspool: %s", strerror(errno));
345c711e483Smillert 		/* NOTREACHED */
346c711e483Smillert 		break;
347c711e483Smillert 	default:
348c711e483Smillert 		/* parent */
349c711e483Smillert 		close(pfd[1]);
350c711e483Smillert 		lockfd = pfd[0];
351c711e483Smillert 		break;
352c711e483Smillert 	}
353c711e483Smillert 
354c711e483Smillert 	if (read(lockfd, &ch, 1) != 1 || ch != '1') {
355c711e483Smillert 		unlockspool();
356e1869fb3Smillert 		merr(EX_OSERR, "lockspool: unable to get lock");
357c711e483Smillert 	}
358c711e483Smillert 
359c711e483Smillert 	return lockfd;
360c711e483Smillert }
361c711e483Smillert 
362c711e483Smillert void
unlockspool(void)363c711e483Smillert unlockspool(void)
364c711e483Smillert {
365c711e483Smillert 	if (lockpid != -1) {
366c711e483Smillert 		waitpid(lockpid, NULL, 0);
367c711e483Smillert 		lockpid = -1;
368c711e483Smillert 	} else {
369c711e483Smillert 		rellock();
370c711e483Smillert 	}
371c711e483Smillert 	close(lockfd);
372c711e483Smillert 	lockfd = -1;
373c711e483Smillert }
374c711e483Smillert 
375df930be7Sderaadt void
usage(void)3765b48e80aSderaadt usage(void)
377df930be7Sderaadt {
378e1869fb3Smillert 	merr(EX_USAGE, "usage: mail.local [-Ll] [-f from] user ...");
379df930be7Sderaadt }
380