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 55int 56main(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 109int 110storemail(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 154int 155deliver(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 */ 183retry: 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 260bad: 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 274void 275notifybiff(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 316static int lockfd = -1; 317static pid_t lockpid = -1; 318 319int 320lockspool(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 362void 363unlockspool(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 375void 376usage(void) 377{ 378 merr(EX_USAGE, "usage: mail.local [-Ll] [-f from] user ..."); 379} 380