File: | src/libexec/mail.local/mail.local.c |
Warning: | line 148, column 6 Access to field '_flags' results in a dereference of a null pointer (loaded from variable 'fp') |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: mail.local.c,v 1.42 2023/06/05 08:07:18 op 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(*__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 | |||
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_PERROR0x20, LOG_MAIL(2<<3)); | |||
64 | ||||
65 | from = NULL((void *)0); | |||
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_USAGE64, "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
| |||
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 | |||
110 | storemail(char *from) | |||
111 | { | |||
112 | FILE *fp = NULL((void *)0); | |||
113 | time_t tval; | |||
114 | int fd, eline = 1; | |||
115 | char *tbuf, *line = NULL((void *)0); | |||
116 | size_t linesize = 0; | |||
117 | ssize_t linelen; | |||
118 | ||||
119 | if ((tbuf = strdup(_PATH_LOCTMP"/tmp/local.XXXXXXXXXX")) == NULL((void *)0)) | |||
120 | merr(EX_OSERR71, "unable to allocate memory"); | |||
121 | if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+"))) | |||
122 | merr(EX_OSERR71, "unable to open temporary file"); | |||
123 | (void)unlink(tbuf); | |||
124 | free(tbuf); | |||
125 | ||||
126 | (void)time(&tval); | |||
127 | (void)fprintf(fp, "From %s %s", from, ctime(&tval)); | |||
128 | ||||
129 | while ((linelen = getline(&line, &linesize, stdin(&__sF[0]))) != -1) { | |||
130 | if (line[linelen - 1] == '\n') | |||
131 | line[linelen - 1] = '\0'; | |||
132 | if (line[0] == '\0') | |||
133 | eline = 1; | |||
134 | else { | |||
135 | if (eline && !strncmp(line, "From ", 5)) | |||
136 | (void)putc('>', fp)(!__isthreaded ? __sputc('>', fp) : (putc)('>', fp)); | |||
137 | eline = 0; | |||
138 | } | |||
139 | (void)fprintf(fp, "%s\n", line); | |||
140 | if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror )(fp))) | |||
141 | break; | |||
142 | } | |||
143 | free(line); | |||
144 | ||||
145 | /* Output a newline; note, empty messages are allowed. */ | |||
146 | (void)putc('\n', fp)(!__isthreaded ? __sputc('\n', fp) : (putc)('\n', fp)); | |||
147 | (void)fflush(fp); | |||
148 | if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror )(fp))) | |||
| ||||
149 | merr(EX_OSERR71, "temporary file write error"); | |||
150 | return(fd); | |||
151 | } | |||
152 | ||||
153 | int | |||
154 | deliver(int fd, char *name, int lockfile) | |||
155 | { | |||
156 | struct stat sb, fsb; | |||
157 | struct passwd *pw; | |||
158 | int mbfd=-1, lfd=-1, rval=EX_OSERR71; | |||
159 | char biffmsg[100], buf[8*1024], path[PATH_MAX1024]; | |||
160 | off_t curoff; | |||
161 | size_t off; | |||
162 | ssize_t nr, nw; | |||
163 | ||||
164 | /* | |||
165 | * Disallow delivery to unknown names -- special mailboxes can be | |||
166 | * handled in the sendmail aliases file. | |||
167 | */ | |||
168 | if (!(pw = getpwnam(name))) { | |||
169 | mwarn("unknown name: %s", name); | |||
170 | return(EX_NOUSER67); | |||
171 | } | |||
172 | ||||
173 | (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR"/var/mail", name); | |||
174 | ||||
175 | if (lockfile) { | |||
176 | lfd = lockspool(name, pw); | |||
177 | if (lfd == -1) | |||
178 | return(EX_OSERR71); | |||
179 | } | |||
180 | ||||
181 | /* after this point, always exit via bad to remove lockfile */ | |||
182 | retry: | |||
183 | if (lstat(path, &sb)) { | |||
184 | if (errno(*__errno()) != ENOENT2) { | |||
185 | mwarn("%s: %s", path, strerror(errno(*__errno()))); | |||
186 | goto bad; | |||
187 | } | |||
188 | if ((mbfd = open(path, O_APPEND0x0008|O_CREAT0x0200|O_EXCL0x0800|O_WRONLY0x0001|O_EXLOCK0x0020, | |||
189 | S_IRUSR0000400|S_IWUSR0000200)) == -1) { | |||
190 | if (errno(*__errno()) == EEXIST17) { | |||
191 | /* file appeared since lstat */ | |||
192 | goto retry; | |||
193 | } else { | |||
194 | mwarn("%s: %s", path, strerror(errno(*__errno()))); | |||
195 | rval = EX_CANTCREAT73; | |||
196 | goto bad; | |||
197 | } | |||
198 | } | |||
199 | /* | |||
200 | * Set the owner and group. Historically, binmail repeated | |||
201 | * this at each mail delivery. We no longer do this, assuming | |||
202 | * that if the ownership or permissions were changed there | |||
203 | * was a reason for doing so. | |||
204 | */ | |||
205 | if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) { | |||
206 | mwarn("chown %u:%u: %s", pw->pw_uid, pw->pw_gid, name); | |||
207 | goto bad; | |||
208 | } | |||
209 | } else { | |||
210 | if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)((sb.st_mode & 0170000) == 0100000)) { | |||
211 | mwarn("%s: linked or special file", path); | |||
212 | goto bad; | |||
213 | } | |||
214 | if ((mbfd = open(path, O_APPEND0x0008|O_WRONLY0x0001|O_EXLOCK0x0020, | |||
215 | S_IRUSR0000400|S_IWUSR0000200)) == -1) { | |||
216 | mwarn("%s: %s", path, strerror(errno(*__errno()))); | |||
217 | goto bad; | |||
218 | } | |||
219 | if (fstat(mbfd, &fsb) == -1) { | |||
220 | /* relating error to path may be bad style */ | |||
221 | mwarn("%s: %s", path, strerror(errno(*__errno()))); | |||
222 | goto bad; | |||
223 | } | |||
224 | if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) { | |||
225 | mwarn("%s: changed after open", path); | |||
226 | goto bad; | |||
227 | } | |||
228 | /* paranoia? */ | |||
229 | if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)((fsb.st_mode & 0170000) == 0100000)) { | |||
230 | mwarn("%s: linked or special file", path); | |||
231 | rval = EX_CANTCREAT73; | |||
232 | goto bad; | |||
233 | } | |||
234 | } | |||
235 | ||||
236 | curoff = lseek(mbfd, 0, SEEK_END2); | |||
237 | (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, | |||
238 | (long long)curoff); | |||
239 | if (lseek(fd, 0, SEEK_SET0) == (off_t)-1) { | |||
240 | mwarn("temporary file: %s", strerror(errno(*__errno()))); | |||
241 | goto bad; | |||
242 | } | |||
243 | ||||
244 | while ((nr = read(fd, buf, sizeof(buf))) > 0) | |||
245 | for (off = 0; off < nr; off += nw) | |||
246 | if ((nw = write(mbfd, buf + off, nr - off)) == -1) { | |||
247 | mwarn("%s: %s", path, strerror(errno(*__errno()))); | |||
248 | (void)ftruncate(mbfd, curoff); | |||
249 | goto bad; | |||
250 | } | |||
251 | ||||
252 | if (nr == 0) { | |||
253 | rval = 0; | |||
254 | } else { | |||
255 | (void)ftruncate(mbfd, curoff); | |||
256 | mwarn("temporary file: %s", strerror(errno(*__errno()))); | |||
257 | } | |||
258 | ||||
259 | bad: | |||
260 | if (lfd != -1) | |||
261 | unlockspool(); | |||
262 | ||||
263 | if (mbfd != -1) { | |||
264 | (void)fsync(mbfd); /* Don't wait for update. */ | |||
265 | (void)close(mbfd); /* Implicit unlock. */ | |||
266 | } | |||
267 | ||||
268 | if (!rval) | |||
269 | notifybiff(biffmsg); | |||
270 | return(rval); | |||
271 | } | |||
272 | ||||
273 | void | |||
274 | notifybiff(char *msg) | |||
275 | { | |||
276 | static struct addrinfo *res0; | |||
277 | struct addrinfo hints, *res; | |||
278 | static int f = -1; | |||
279 | size_t len; | |||
280 | int error; | |||
281 | ||||
282 | if (res0 == NULL((void *)0)) { | |||
283 | memset(&hints, 0, sizeof(hints)); | |||
284 | hints.ai_family = PF_UNSPEC0; | |||
285 | hints.ai_socktype = SOCK_DGRAM2; | |||
286 | ||||
287 | error = getaddrinfo("localhost", "biff", &hints, &res0); | |||
288 | if (error) { | |||
289 | /* Be silent if biff service not available. */ | |||
290 | if (error != EAI_SERVICE-8) { | |||
291 | mwarn("localhost: %s", gai_strerror(error)); | |||
292 | } | |||
293 | return; | |||
294 | } | |||
295 | } | |||
296 | ||||
297 | if (f == -1) { | |||
298 | for (res = res0; res != NULL((void *)0); res = res->ai_next) { | |||
299 | f = socket(res->ai_family, res->ai_socktype, | |||
300 | res->ai_protocol); | |||
301 | if (f != -1) | |||
302 | break; | |||
303 | } | |||
304 | } | |||
305 | if (f == -1) { | |||
306 | mwarn("socket: %s", strerror(errno(*__errno()))); | |||
307 | return; | |||
308 | } | |||
309 | ||||
310 | len = strlen(msg) + 1; /* XXX */ | |||
311 | if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len) | |||
312 | mwarn("sendto biff: %s", strerror(errno(*__errno()))); | |||
313 | } | |||
314 | ||||
315 | static int lockfd = -1; | |||
316 | static pid_t lockpid = -1; | |||
317 | ||||
318 | int | |||
319 | lockspool(const char *name, struct passwd *pw) | |||
320 | { | |||
321 | int pfd[2]; | |||
322 | char ch; | |||
323 | ||||
324 | if (geteuid() == 0) | |||
325 | return getlock(name, pw); | |||
326 | ||||
327 | /* If not privileged, open pipe to lockspool(1) instead */ | |||
328 | if (pipe2(pfd, O_CLOEXEC0x10000) == -1) { | |||
329 | merr(EX_OSERR71, "pipe: %s", strerror(errno(*__errno()))); | |||
330 | return -1; | |||
331 | } | |||
332 | ||||
333 | signal(SIGPIPE13, SIG_IGN(void (*)(int))1); | |||
334 | switch ((lockpid = fork())) { | |||
335 | case -1: | |||
336 | merr(EX_OSERR71, "fork: %s", strerror(errno(*__errno()))); | |||
337 | return -1; | |||
338 | case 0: | |||
339 | /* child */ | |||
340 | close(pfd[0]); | |||
341 | dup2(pfd[1], STDOUT_FILENO1); | |||
342 | execl(_PATH_LOCKSPOOL"/usr/libexec/lockspool", "lockspool", (char *)NULL((void *)0)); | |||
343 | merr(EX_OSERR71, "execl: lockspool: %s", strerror(errno(*__errno()))); | |||
344 | /* NOTREACHED */ | |||
345 | break; | |||
346 | default: | |||
347 | /* parent */ | |||
348 | close(pfd[1]); | |||
349 | lockfd = pfd[0]; | |||
350 | break; | |||
351 | } | |||
352 | ||||
353 | if (read(lockfd, &ch, 1) != 1 || ch != '1') { | |||
354 | unlockspool(); | |||
355 | merr(EX_OSERR71, "lockspool: unable to get lock"); | |||
356 | } | |||
357 | ||||
358 | return lockfd; | |||
359 | } | |||
360 | ||||
361 | void | |||
362 | unlockspool(void) | |||
363 | { | |||
364 | if (lockpid != -1) { | |||
365 | waitpid(lockpid, NULL((void *)0), 0); | |||
366 | lockpid = -1; | |||
367 | } else { | |||
368 | rellock(); | |||
369 | } | |||
370 | close(lockfd); | |||
371 | lockfd = -1; | |||
372 | } | |||
373 | ||||
374 | void | |||
375 | usage(void) | |||
376 | { | |||
377 | merr(EX_USAGE64, "usage: mail.local [-Ll] [-f from] user ..."); | |||
378 | } |