xref: /openbsd/libexec/mail.local/mail.local.c (revision 9b7c3dbb)
1 /*	$OpenBSD: mail.local.c,v 1.35 2015/12/12 20:09:28 mmcc Exp $	*/
2 
3 /*-
4  * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com>
5  * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu>
6  * Copyright (c) 1990 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/socket.h>
37 #include <netinet/in.h>
38 #include <syslog.h>
39 #include <fcntl.h>
40 #include <netdb.h>
41 #include <pwd.h>
42 #include <time.h>
43 #include <unistd.h>
44 #include <limits.h>
45 #include <errno.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include "pathnames.h"
50 #include "mail.local.h"
51 
52 int
53 main(int argc, char *argv[])
54 {
55 	struct passwd *pw;
56 	int ch, fd, eval, lockfile=1, holdme=0;
57 	uid_t uid;
58 	char *from;
59 
60 	openlog("mail.local", LOG_PERROR, LOG_MAIL);
61 
62 	from = NULL;
63 	while ((ch = getopt(argc, argv, "lLdf:r:H")) != -1)
64 		switch (ch) {
65 		case 'd':		/* backward compatible */
66 			break;
67 		case 'f':
68 		case 'r':		/* backward compatible */
69 			if (from)
70 				merr(FATAL, "multiple -f options");
71 			from = optarg;
72 			break;
73 		case 'l':
74 			lockfile=1;
75 			break;
76 		case 'L':
77 			lockfile=0;
78 			break;
79 		case 'H':
80 			holdme=1;
81 			break;
82 		default:
83 			usage();
84 		}
85 	argc -= optind;
86 	argv += optind;
87 
88 	/* Support -H flag for backwards compat */
89 	if (holdme) {
90 		execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL);
91 		merr(FATAL, "execl: lockspool: %s", strerror(errno));
92 	} else {
93 		if (!*argv)
94 			usage();
95 		if (geteuid() != 0)
96 			merr(FATAL, "may only be run by the superuser");
97 	}
98 
99 	/*
100 	 * If from not specified, use the name from getlogin() if the
101 	 * uid matches, otherwise, use the name from the password file
102 	 * corresponding to the uid.
103 	 */
104 	uid = getuid();
105 	if (!from && (!(from = getlogin()) ||
106 	    !(pw = getpwnam(from)) || pw->pw_uid != uid))
107 		from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
108 
109 	fd = storemail(from);
110 	for (eval = 0; *argv; ++argv)
111 		eval |= deliver(fd, *argv, lockfile);
112 	exit(eval);
113 }
114 
115 int
116 storemail(char *from)
117 {
118 	FILE *fp = NULL;
119 	time_t tval;
120 	int fd, eline;
121 	size_t len;
122 	char *line, *tbuf;
123 
124 	if ((tbuf = strdup(_PATH_LOCTMP)) == NULL)
125 		merr(FATAL, "unable to allocate memory");
126 	if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+")))
127 		merr(FATAL, "unable to open temporary file");
128 	(void)unlink(tbuf);
129 	free(tbuf);
130 
131 	(void)time(&tval);
132 	(void)fprintf(fp, "From %s %s", from, ctime(&tval));
133 
134 	for (eline = 1, tbuf = NULL; (line = fgetln(stdin, &len));) {
135 		/* We have to NUL-terminate the line since fgetln does not */
136 		if (line[len - 1] == '\n')
137 			line[len - 1] = '\0';
138 		else {
139 			/* No trailing newline, so alloc space and copy */
140 			if ((tbuf = malloc(len + 1)) == NULL)
141 				merr(FATAL, "unable to allocate memory");
142 			memcpy(tbuf, line, len);
143 			tbuf[len] = '\0';
144 			line = tbuf;
145 		}
146 		if (line[0] == '\0')
147 			eline = 1;
148 		else {
149 			if (eline && line[0] == 'F' && len > 5 &&
150 			    !memcmp(line, "From ", 5))
151 				(void)putc('>', fp);
152 			eline = 0;
153 		}
154 		(void)fprintf(fp, "%s\n", line);
155 		if (ferror(fp))
156 			break;
157 	}
158 	free(tbuf);
159 
160 	/* Output a newline; note, empty messages are allowed. */
161 	(void)putc('\n', fp);
162 	(void)fflush(fp);
163 	if (ferror(fp))
164 		merr(FATAL, "temporary file write error");
165 	return(fd);
166 }
167 
168 int
169 deliver(int fd, char *name, int lockfile)
170 {
171 	struct stat sb, fsb;
172 	struct passwd *pw;
173 	int mbfd=-1, rval=1, lfd=-1;
174 	char biffmsg[100], buf[8*1024], path[PATH_MAX];
175 	off_t curoff;
176 	size_t off;
177 	ssize_t nr, nw;
178 
179 	/*
180 	 * Disallow delivery to unknown names -- special mailboxes can be
181 	 * handled in the sendmail aliases file.
182 	 */
183 	if (!(pw = getpwnam(name))) {
184 		merr(NOTFATAL, "unknown name: %s", name);
185 		return(1);
186 	}
187 
188 	(void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name);
189 
190 	if (lockfile) {
191 		lfd = getlock(name, pw);
192 		if (lfd == -1)
193 			return (1);
194 	}
195 
196 	/* after this point, always exit via bad to remove lockfile */
197 retry:
198 	if (lstat(path, &sb)) {
199 		if (errno != ENOENT) {
200 			merr(NOTFATAL, "%s: %s", path, strerror(errno));
201 			goto bad;
202 		}
203 		if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK,
204 		    S_IRUSR|S_IWUSR)) < 0) {
205 			if (errno == EEXIST) {
206 				/* file appeared since lstat */
207 				goto retry;
208 			} else {
209 				merr(NOTFATAL, "%s: %s", path, strerror(errno));
210 				goto bad;
211 			}
212 		}
213 		/*
214 		 * Set the owner and group.  Historically, binmail repeated
215 		 * this at each mail delivery.  We no longer do this, assuming
216 		 * that if the ownership or permissions were changed there
217 		 * was a reason for doing so.
218 		 */
219 		if (fchown(mbfd, pw->pw_uid, pw->pw_gid) < 0) {
220 			merr(NOTFATAL, "chown %u:%u: %s",
221 			    pw->pw_uid, pw->pw_gid, name);
222 			goto bad;
223 		}
224 	} else {
225 		if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) {
226 			merr(NOTFATAL, "%s: linked or special file", path);
227 			goto bad;
228 		}
229 		if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK,
230 		    S_IRUSR|S_IWUSR)) < 0) {
231 			merr(NOTFATAL, "%s: %s", path, strerror(errno));
232 			goto bad;
233 		}
234 		if (fstat(mbfd, &fsb)) {
235 			/* relating error to path may be bad style */
236 			merr(NOTFATAL, "%s: %s", path, strerror(errno));
237 			goto bad;
238 		}
239 		if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) {
240 			merr(NOTFATAL, "%s: changed after open", path);
241 			goto bad;
242 		}
243 		/* paranoia? */
244 		if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) {
245 			merr(NOTFATAL, "%s: linked or special file", path);
246 			goto bad;
247 		}
248 	}
249 
250 	curoff = lseek(mbfd, 0, SEEK_END);
251 	(void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, curoff);
252 	if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
253 		merr(NOTFATAL, "temporary file: %s", strerror(errno));
254 		goto bad;
255 	}
256 
257 	while ((nr = read(fd, buf, sizeof(buf))) > 0)
258 		for (off = 0; off < nr;  off += nw)
259 			if ((nw = write(mbfd, buf + off, nr - off)) < 0) {
260 				merr(NOTFATAL, "%s: %s", path, strerror(errno));
261 				(void)ftruncate(mbfd, curoff);
262 				goto bad;
263 			}
264 
265 	if (nr == 0) {
266 		rval = 0;
267 	} else {
268 		(void)ftruncate(mbfd, curoff);
269 		merr(FATAL, "temporary file: %s", strerror(errno));
270 	}
271 
272 bad:
273 	if (lfd != -1) {
274 		rellock();
275 		close(lfd);
276 	}
277 
278 	if (mbfd != -1) {
279 		(void)fsync(mbfd);		/* Don't wait for update. */
280 		(void)close(mbfd);		/* Implicit unlock. */
281 	}
282 
283 	if (!rval)
284 		notifybiff(biffmsg);
285 	return(rval);
286 }
287 
288 void
289 notifybiff(char *msg)
290 {
291 	static struct addrinfo *res0;
292 	struct addrinfo hints, *res;
293 	static int f = -1;
294 	size_t len;
295 	int error;
296 
297 	if (res0 == NULL) {
298 		memset(&hints, 0, sizeof(hints));
299 		hints.ai_family = PF_UNSPEC;
300 		hints.ai_socktype = SOCK_DGRAM;
301 
302 		error = getaddrinfo("localhost", "biff", &hints, &res0);
303 		if (error) {
304 			/* Be silent if biff service not available. */
305 			if (error != EAI_SERVICE) {
306 				merr(NOTFATAL, "localhost: %s",
307 				    gai_strerror(error));
308 			}
309 			return;
310 		}
311 	}
312 
313 	if (f == -1) {
314 		for (res = res0; res != NULL; res = res->ai_next) {
315 			f = socket(res->ai_family, res->ai_socktype,
316 			    res->ai_protocol);
317 			if (f != -1)
318 				break;
319 		}
320 	}
321 	if (f == -1) {
322 		merr(NOTFATAL, "socket: %s", strerror(errno));
323 		return;
324 	}
325 
326 	len = strlen(msg) + 1;	/* XXX */
327 	if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len)
328 		merr(NOTFATAL, "sendto biff: %s", strerror(errno));
329 }
330 
331 void
332 usage(void)
333 {
334 	merr(FATAL, "usage: mail.local [-Ll] [-f from] user ...");
335 }
336