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