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