1 /* $OpenBSD: mail.local.c,v 1.39 2020/02/09 14:59:20 millert 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 "includes.h"
35
36 #include <sys/types.h>
37
38 #include <sys/stat.h>
39 #include <sys/socket.h>
40 #include <sys/wait.h>
41 #include <netinet/in.h>
42 #include <sysexits.h>
43 #include <syslog.h>
44 #include <fcntl.h>
45 #include <netdb.h>
46 #include <pwd.h>
47 #include <time.h>
48 #include <unistd.h>
49 #include <limits.h>
50 #include <errno.h>
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <signal.h>
55 #include "pathnames.h"
56 #include "mail.local.h"
57
58 int
main(int argc,char * argv[])59 main(int argc, char *argv[])
60 {
61 struct passwd *pw;
62 int ch, fd, eval, lockfile=1;
63 uid_t uid;
64 char *from;
65
66 openlog("mail.local", LOG_PERROR, LOG_MAIL);
67
68 from = NULL;
69 while ((ch = getopt(argc, argv, "lLdf:r:")) != -1)
70 switch (ch) {
71 case 'd': /* backward compatible */
72 break;
73 case 'f':
74 case 'r': /* backward compatible */
75 if (from)
76 merr(EX_USAGE, "multiple -f options");
77 from = optarg;
78 break;
79 case 'l':
80 lockfile=1;
81 break;
82 case 'L':
83 lockfile=0;
84 break;
85 default:
86 usage();
87 }
88 argc -= optind;
89 argv += optind;
90
91 if (!*argv)
92 usage();
93
94 /*
95 * If from not specified, use the name from getlogin() if the
96 * uid matches, otherwise, use the name from the password file
97 * corresponding to the uid.
98 */
99 uid = getuid();
100 if (!from && (!(from = getlogin()) ||
101 !(pw = getpwnam(from)) || pw->pw_uid != uid))
102 from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
103
104 fd = storemail(from);
105 for (eval = 0; *argv; ++argv) {
106 if ((ch = deliver(fd, *argv, lockfile)) != 0)
107 eval = ch;
108 }
109 exit(eval);
110 }
111
112 int
storemail(char * from)113 storemail(char *from)
114 {
115 FILE *fp = NULL;
116 time_t tval;
117 int fd, eline;
118 size_t len;
119 char *line, *tbuf;
120
121 if ((tbuf = strdup(_PATH_LOCTMP)) == NULL)
122 merr(EX_OSERR, "unable to allocate memory");
123 if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+")))
124 merr(EX_OSERR, "unable to open temporary file");
125 (void)unlink(tbuf);
126 free(tbuf);
127
128 (void)time(&tval);
129 (void)fprintf(fp, "From %s %s", from, ctime(&tval));
130
131 for (eline = 1, tbuf = NULL; (line = fgetln(stdin, &len));) {
132 /* We have to NUL-terminate the line since fgetln does not */
133 if (line[len - 1] == '\n')
134 line[len - 1] = '\0';
135 else {
136 /* No trailing newline, so alloc space and copy */
137 if ((tbuf = malloc(len + 1)) == NULL)
138 merr(EX_OSERR, "unable to allocate memory");
139 memcpy(tbuf, line, len);
140 tbuf[len] = '\0';
141 line = tbuf;
142 }
143 if (line[0] == '\0')
144 eline = 1;
145 else {
146 if (eline && line[0] == 'F' && len > 5 &&
147 !memcmp(line, "From ", 5))
148 (void)putc('>', fp);
149 eline = 0;
150 }
151 (void)fprintf(fp, "%s\n", line);
152 if (ferror(fp))
153 break;
154 }
155 free(tbuf);
156
157 /* Output a newline; note, empty messages are allowed. */
158 (void)putc('\n', fp);
159 (void)fflush(fp);
160 if (ferror(fp))
161 merr(EX_OSERR, "temporary file write error");
162 return(fd);
163 }
164
165 int
deliver(int fd,char * name,int lockfile)166 deliver(int fd, char *name, int lockfile)
167 {
168 struct stat sb, fsb;
169 struct passwd *pw;
170 int mbfd=-1, lfd=-1, rval=EX_OSERR;
171 char biffmsg[100], buf[8*1024], path[PATH_MAX];
172 off_t curoff;
173 size_t off;
174 ssize_t nr, nw;
175
176 /*
177 * Disallow delivery to unknown names -- special mailboxes can be
178 * handled in the sendmail aliases file.
179 */
180 if (!(pw = getpwnam(name))) {
181 mwarn("unknown name: %s", name);
182 return(EX_NOUSER);
183 }
184
185 (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name);
186
187 if (lockfile) {
188 lfd = lockspool(name, pw);
189 if (lfd == -1)
190 return(EX_OSERR);
191 }
192
193 /* after this point, always exit via bad to remove lockfile */
194 retry:
195 if (lstat(path, &sb)) {
196 if (errno != ENOENT) {
197 mwarn("%s: %s", path, strerror(errno));
198 goto bad;
199 }
200 if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK,
201 S_IRUSR|S_IWUSR)) == -1) {
202 #ifndef HAVE_O_EXLOCK
203 /* XXX : do something! */
204 #endif
205 if (errno == EEXIST) {
206 /* file appeared since lstat */
207 goto retry;
208 } else {
209 mwarn("%s: %s", path, strerror(errno));
210 rval = EX_CANTCREAT;
211 goto bad;
212 }
213 }
214 /*
215 * Set the owner and group. Historically, binmail repeated
216 * this at each mail delivery. We no longer do this, assuming
217 * that if the ownership or permissions were changed there
218 * was a reason for doing so.
219 */
220 if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) {
221 mwarn("chown %u:%u: %s", 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 mwarn("%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)) == -1) {
231 mwarn("%s: %s", path, strerror(errno));
232 goto bad;
233 }
234 if (fstat(mbfd, &fsb) == -1) {
235 /* relating error to path may be bad style */
236 mwarn("%s: %s", path, strerror(errno));
237 goto bad;
238 }
239 if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) {
240 mwarn("%s: changed after open", path);
241 goto bad;
242 }
243 /* paranoia? */
244 if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) {
245 mwarn("%s: linked or special file", path);
246 rval = EX_CANTCREAT;
247 goto bad;
248 }
249 }
250
251 curoff = lseek(mbfd, 0, SEEK_END);
252 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, curoff);
253 if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
254 mwarn("temporary file: %s", strerror(errno));
255 goto bad;
256 }
257
258 while ((nr = read(fd, buf, sizeof(buf))) > 0)
259 for (off = 0; off < nr; off += nw)
260 if ((nw = write(mbfd, buf + off, nr - off)) == -1) {
261 mwarn("%s: %s", path, strerror(errno));
262 (void)ftruncate(mbfd, curoff);
263 goto bad;
264 }
265
266 if (nr == 0) {
267 rval = 0;
268 } else {
269 (void)ftruncate(mbfd, curoff);
270 mwarn("temporary file: %s", strerror(errno));
271 }
272
273 bad:
274 if (lfd != -1)
275 unlockspool();
276
277 if (mbfd != -1) {
278 (void)fsync(mbfd); /* Don't wait for update. */
279 (void)close(mbfd); /* Implicit unlock. */
280 }
281
282 if (!rval)
283 notifybiff(biffmsg);
284 return(rval);
285 }
286
287 void
notifybiff(char * msg)288 notifybiff(char *msg)
289 {
290 static struct addrinfo *res0;
291 struct addrinfo hints, *res;
292 static int f = -1;
293 size_t len;
294 int error;
295
296 if (res0 == NULL) {
297 memset(&hints, 0, sizeof(hints));
298 hints.ai_family = PF_UNSPEC;
299 hints.ai_socktype = SOCK_DGRAM;
300
301 error = getaddrinfo("localhost", "biff", &hints, &res0);
302 if (error) {
303 /* Be silent if biff service not available. */
304 if (error != EAI_SERVICE) {
305 mwarn("localhost: %s", gai_strerror(error));
306 }
307 return;
308 }
309 }
310
311 if (f == -1) {
312 for (res = res0; res != NULL; res = res->ai_next) {
313 f = socket(res->ai_family, res->ai_socktype,
314 res->ai_protocol);
315 if (f != -1)
316 break;
317 }
318 }
319 if (f == -1) {
320 mwarn("socket: %s", strerror(errno));
321 return;
322 }
323
324 len = strlen(msg) + 1; /* XXX */
325 if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len)
326 mwarn("sendto biff: %s", strerror(errno));
327 }
328
329 static int lockfd = -1;
330 static pid_t lockpid = -1;
331
332 int
lockspool(const char * name,struct passwd * pw)333 lockspool(const char *name, struct passwd *pw)
334 {
335 int pfd[2];
336 char ch;
337
338 if (geteuid() == 0)
339 return getlock(name, pw);
340
341 /* If not privileged, open pipe to lockspool(1) instead */
342 if (pipe2(pfd, O_CLOEXEC) == -1) {
343 merr(EX_OSERR, "pipe: %s", strerror(errno));
344 return -1;
345 }
346
347 signal(SIGPIPE, SIG_IGN);
348 switch ((lockpid = fork())) {
349 case -1:
350 merr(EX_OSERR, "fork: %s", strerror(errno));
351 return -1;
352 case 0:
353 /* child */
354 close(pfd[0]);
355 dup2(pfd[1], STDOUT_FILENO);
356 execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL);
357 merr(EX_OSERR, "execl: lockspool: %s", strerror(errno));
358 /* NOTREACHED */
359 break;
360 default:
361 /* parent */
362 close(pfd[1]);
363 lockfd = pfd[0];
364 break;
365 }
366
367 if (read(lockfd, &ch, 1) != 1 || ch != '1') {
368 unlockspool();
369 merr(EX_OSERR, "lockspool: unable to get lock");
370 }
371
372 return lockfd;
373 }
374
375 void
unlockspool(void)376 unlockspool(void)
377 {
378 if (lockpid != -1) {
379 waitpid(lockpid, NULL, 0);
380 lockpid = -1;
381 } else {
382 rellock();
383 }
384 close(lockfd);
385 lockfd = -1;
386 }
387
388 void
usage(void)389 usage(void)
390 {
391 merr(EX_USAGE, "usage: mail.local [-Ll] [-f from] user ...");
392 }
393