1/* $NetBSD: mail.local.c,v 1.33 2023/09/06 22:08:38 christos Exp $ */ 2 3/*- 4 * Copyright (c) 1990, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\ 35 The Regents of the University of California. All rights reserved."); 36#if 0 37static char sccsid[] = "@(#)mail.local.c 8.22 (Berkeley) 6/21/95"; 38#else 39__RCSID("$NetBSD: mail.local.c,v 1.33 2023/09/06 22:08:38 christos Exp $"); 40#endif 41#endif /* not lint */ 42 43#include <sys/param.h> 44#include <sys/stat.h> 45#include <sys/socket.h> 46 47#include <netinet/in.h> 48 49#include <errno.h> 50#include <fcntl.h> 51#include <pwd.h> 52#include <netdb.h> 53#include <stdarg.h> 54#include <stdbool.h> 55#include <stdio.h> 56#include <stdlib.h> 57#include <string.h> 58#include <syslog.h> 59#include <time.h> 60#include <unistd.h> 61#include <sysexits.h> 62 63 64#include "pathnames.h" 65 66static int deliver(int, char *, int); 67__dead static void logerr(int, const char *, ...) __printflike(2, 3); 68static void logwarn(const char *, ...) __printflike(1, 2); 69static void notifybiff(char *); 70static int store(const char *); 71__dead static void usage(void); 72 73int 74main(int argc, char *argv[]) 75{ 76 struct passwd *pw; 77 int ch, fd, eval, lockfile = 0; 78 uid_t uid; 79 const char *from; 80 81 /* use a reasonable umask */ 82 (void) umask(0077); 83 84 openlog("mail.local", LOG_PERROR, LOG_MAIL); 85 86 from = NULL; 87 while ((ch = getopt(argc, argv, "ldf:r:")) != -1) 88 switch (ch) { 89 case 'd': /* backward compatible */ 90 break; 91 case 'f': 92 case 'r': /* backward compatible */ 93 if (from) 94 logerr(EX_USAGE, "multiple -f options"); 95 from = optarg; 96 break; 97 case 'l': 98 lockfile++; 99 break; 100 case '?': 101 default: 102 usage(); 103 } 104 argc -= optind; 105 argv += optind; 106 107 if (!*argv) 108 usage(); 109 110 /* 111 * If from not specified, use the name from getlogin() if the 112 * uid matches, otherwise, use the name from the password file 113 * corresponding to the uid. 114 */ 115 uid = getuid(); 116 if (!from && (!(from = getlogin()) || 117 !(pw = getpwnam(from)) || pw->pw_uid != uid)) 118 from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 119 120 fd = store(from); 121 for (eval = EX_OK; *argv; ++argv) { 122 int rval; 123 124 rval = deliver(fd, *argv, lockfile); 125 if (eval == EX_OK && rval != EX_OK) 126 eval = rval; 127 } 128 return eval; 129} 130 131static int 132store(const char *from) 133{ 134 FILE *fp = NULL; /* XXX gcc */ 135 time_t tval; 136 int fd, eline; 137 char *tn, line[2048]; 138 139 tn = strdup(_PATH_LOCTMP); 140 if (!tn) 141 logerr(EX_OSERR, "not enough memory"); 142 if ((fd = mkstemp(tn)) == -1 || !(fp = fdopen(fd, "w+"))) 143 logerr(EX_OSERR, "unable to open temporary file"); 144 (void)unlink(tn); 145 free(tn); 146 147 (void)time(&tval); 148 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 149 150 line[0] = '\0'; 151 for (eline = 1; fgets(line, sizeof(line), stdin);) { 152 if (line[0] == '\n') 153 eline = 1; 154 else { 155 if (eline && line[0] == 'F' && !memcmp(line, "From ", 5)) 156 (void)putc('>', fp); 157 eline = 0; 158 } 159 (void)fprintf(fp, "%s", line); 160 if (ferror(fp)) 161 break; 162 } 163 164 /* If message not newline terminated, need an extra. */ 165 if (!index(line, '\n')) 166 (void)putc('\n', fp); 167 /* Output a newline; note, empty messages are allowed. */ 168 (void)putc('\n', fp); 169 170 (void)fflush(fp); 171 if (ferror(fp)) 172 logerr(EX_OSERR, "temporary file write error"); 173 if ((fd = dup(fd)) == -1) 174 logerr(EX_OSERR, "dup failed"); 175 (void)fclose(fp); 176 return fd; 177} 178 179static bool 180badfile(const char *path, const struct stat *sb) 181{ 182 if (!S_ISREG(sb->st_mode)) { 183 logwarn("%s: not a regular file", path); 184 return true; 185 } 186 187 if (sb->st_nlink != 1) { 188 logwarn("%s: linked file", path); 189 return true; 190 } 191 return false; 192} 193 194static int 195deliver(int fd, char *name, int lockfile) 196{ 197 struct stat sb, nsb; 198 struct passwd pwres, *pw; 199 char pwbuf[1024]; 200 int created = 0, mbfd = -1, nr, nw, off, rval = EX_OSERR, lfd = -1; 201 char biffmsg[100], buf[8*1024], path[MAXPATHLEN], lpath[MAXPATHLEN]; 202 off_t curoff; 203 204 /* 205 * Disallow delivery to unknown names -- special mailboxes can be 206 * handled in the sendmail aliases file. 207 */ 208 if ((getpwnam_r(name, &pwres, pwbuf, sizeof(pwbuf), &pw)) != 0) { 209 logwarn("unable to find user %s: %s", name, strerror(errno)); 210 return EX_TEMPFAIL; 211 } 212 if (pw == NULL) { 213 logwarn("unknown name: %s", name); 214 return EX_NOUSER; 215 } 216 217 (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name); 218 219 if (lockfile) { 220 (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", 221 _PATH_MAILDIR, name); 222 223 if((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL, 224 S_IRUSR|S_IWUSR)) < 0) { 225 logwarn("%s: can't create: %s", lpath, strerror(errno)); 226 return EX_OSERR; 227 } 228 } 229 230 if (lstat(path, &sb) == -1) { 231 if (errno != ENOENT) { 232 logwarn("%s: can't stat: %s", path, strerror(errno)); 233 goto bad; 234 } 235 memset(&sb, 0, sizeof(sb)); 236 sb.st_dev = NODEV; 237 } else if (badfile(path, &sb)) { 238 goto bad; 239 } 240 241 if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK|O_NOFOLLOW, 242 S_IRUSR|S_IWUSR)) == -1) { 243 /* create file */ 244 if (errno != ENOENT || 245 (mbfd = open(path, O_APPEND|O_CREAT|O_WRONLY|O_EXLOCK|O_EXCL, 246 S_IRUSR|S_IWUSR)) == -1) { 247 logwarn("%s: can't create: %s", path, strerror(errno)); 248 goto bad; 249 } 250 created = 1; 251 } else { 252 /* opened existing file, check for TOCTTOU */ 253 if (fstat(mbfd, &nsb) == -1) { 254 logwarn("%s: can't stat: %s", path, strerror(errno)); 255 goto bad; 256 } 257 258 if (badfile(path, &nsb)) { 259 goto bad; 260 } 261 262 /* file is not what we expected */ 263 if (nsb.st_ino != sb.st_ino || nsb.st_dev != sb.st_dev) { 264 logwarn("%s: file has changed", path); 265 goto bad; 266 } 267 } 268 269 if ((curoff = lseek(mbfd, 0, SEEK_END)) == (off_t)-1) { 270 logwarn("%s: can't seek: %s", path, strerror(errno)); 271 goto bad; 272 } 273 274 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, 275 (long long)curoff); 276 if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { 277 logwarn("can't seek: %s", strerror(errno)); 278 goto bad; 279 } 280 281 while ((nr = read(fd, buf, sizeof(buf))) > 0) 282 for (off = 0; off < nr; off += nw) 283 if ((nw = write(mbfd, buf + off, nr - off)) < 0) { 284 logwarn("%s: can't write: %s", path, 285 strerror(errno)); 286 goto trunc; 287 } 288 289 if (nr < 0) { 290 logwarn("can't read: %s", strerror(errno)); 291trunc: (void)ftruncate(mbfd, curoff); 292 goto bad; 293 } 294 295 rval = EX_OK; 296 /* 297 * Set the owner and group. Historically, binmail repeated this at 298 * each mail delivery. We no longer do this, assuming that if the 299 * ownership or permissions were changed there was a reason for doing 300 * so. 301 */ 302bad: 303 if (lockfile) { 304 if (lfd >= 0) { 305 unlink(lpath); 306 close(lfd); 307 } 308 } 309 310 if (mbfd >= 0) { 311 if (created) 312 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 313 314 (void)fsync(mbfd); /* Don't wait for update. */ 315 (void)close(mbfd); /* Implicit unlock. */ 316 } 317 318 if (rval == EX_OK) 319 notifybiff(biffmsg); 320 321 return rval; 322} 323 324void 325notifybiff(char *msg) 326{ 327 static struct sockaddr_in addr; 328 static int f = -1; 329 struct hostent *hp; 330 struct servent *sp; 331 int len; 332 333 if (!addr.sin_family) { 334 /* Be silent if biff service not available. */ 335 if (!(sp = getservbyname("biff", "udp"))) 336 return; 337 if (!(hp = gethostbyname("localhost"))) { 338 logwarn("localhost: %s", strerror(errno)); 339 return; 340 } 341 addr.sin_len = sizeof(struct sockaddr_in); 342 addr.sin_family = hp->h_addrtype; 343 addr.sin_port = sp->s_port; 344 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); 345 } 346 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 347 logwarn("socket: %s", strerror(errno)); 348 return; 349 } 350 len = strlen(msg) + 1; 351 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 352 != len) 353 logwarn("sendto biff: %s", strerror(errno)); 354} 355 356static void 357usage(void) 358{ 359 logerr(EX_USAGE, "usage: mail.local [-l] [-f from] user ..."); 360} 361 362static void 363logerr(int status, const char *fmt, ...) 364{ 365 va_list ap; 366 367 va_start(ap, fmt); 368 vsyslog(LOG_ERR, fmt, ap); 369 va_end(ap); 370 371 exit(status); 372 /* NOTREACHED */ 373} 374 375static void 376logwarn(const char *fmt, ...) 377{ 378 va_list ap; 379 380 va_start(ap, fmt); 381 vsyslog(LOG_ERR, fmt, ap); 382 va_end(ap); 383 return; 384} 385