1 /* $NetBSD: mail.local.c,v 1.29 2022/05/17 11:18:58 kre 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 37 static char sccsid[] = "@(#)mail.local.c 8.22 (Berkeley) 6/21/95"; 38 #else 39 __RCSID("$NetBSD: mail.local.c,v 1.29 2022/05/17 11:18:58 kre 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 <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <syslog.h> 58 #include <time.h> 59 #include <unistd.h> 60 #include <sysexits.h> 61 62 63 #include "pathnames.h" 64 65 static int deliver(int, char *, int); 66 __dead static void logerr(int, const char *, ...) __printflike(2, 3); 67 static void logwarn(const char *, ...) __printflike(1, 2); 68 static void notifybiff(char *); 69 static int store(const char *); 70 __dead static void usage(void); 71 72 int 73 main(int argc, char *argv[]) 74 { 75 struct passwd *pw; 76 int ch, fd, eval, lockfile = 0; 77 uid_t uid; 78 const char *from; 79 80 /* use a reasonable umask */ 81 (void) umask(0077); 82 83 openlog("mail.local", LOG_PERROR, LOG_MAIL); 84 85 from = NULL; 86 while ((ch = getopt(argc, argv, "ldf:r:")) != -1) 87 switch (ch) { 88 case 'd': /* backward compatible */ 89 break; 90 case 'f': 91 case 'r': /* backward compatible */ 92 if (from) 93 logerr(EX_USAGE, "multiple -f options"); 94 from = optarg; 95 break; 96 case 'l': 97 lockfile++; 98 break; 99 case '?': 100 default: 101 usage(); 102 } 103 argc -= optind; 104 argv += optind; 105 106 if (!*argv) 107 usage(); 108 109 /* 110 * If from not specified, use the name from getlogin() if the 111 * uid matches, otherwise, use the name from the password file 112 * corresponding to the uid. 113 */ 114 uid = getuid(); 115 if (!from && (!(from = getlogin()) || 116 !(pw = getpwnam(from)) || pw->pw_uid != uid)) 117 from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 118 119 fd = store(from); 120 for (eval = EX_OK; *argv; ++argv) { 121 int rval; 122 123 rval = deliver(fd, *argv, lockfile); 124 if (eval == EX_OK && rval != EX_OK) 125 eval = rval; 126 } 127 exit (eval); 128 } 129 130 static int 131 store(const char *from) 132 { 133 FILE *fp = NULL; /* XXX gcc */ 134 time_t tval; 135 int fd, eline; 136 char *tn, line[2048]; 137 138 tn = strdup(_PATH_LOCTMP); 139 if (!tn) 140 logerr(EX_OSERR, "not enough core"); 141 if ((fd = mkstemp(tn)) == -1 || !(fp = fdopen(fd, "w+"))) 142 logerr(EX_OSERR, "unable to open temporary file"); 143 (void)unlink(tn); 144 free(tn); 145 146 (void)time(&tval); 147 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 148 149 line[0] = '\0'; 150 for (eline = 1; fgets(line, sizeof(line), stdin);) { 151 if (line[0] == '\n') 152 eline = 1; 153 else { 154 if (eline && line[0] == 'F' && !memcmp(line, "From ", 5)) 155 (void)putc('>', fp); 156 eline = 0; 157 } 158 (void)fprintf(fp, "%s", line); 159 if (ferror(fp)) 160 break; 161 } 162 163 /* If message not newline terminated, need an extra. */ 164 if (!index(line, '\n')) 165 (void)putc('\n', fp); 166 /* Output a newline; note, empty messages are allowed. */ 167 (void)putc('\n', fp); 168 169 (void)fflush(fp); 170 if (ferror(fp)) 171 logerr(EX_OSERR, "temporary file write error"); 172 if ((fd = dup(fd)) == -1) 173 logerr(EX_OSERR, "dup failed"); 174 (void)fclose(fp); 175 return(fd); 176 } 177 178 static int 179 deliver(int fd, char *name, int lockfile) 180 { 181 struct stat sb, nsb; 182 struct passwd pwres, *pw; 183 char pwbuf[1024]; 184 int created = 0, mbfd, nr, nw, off, rval=EX_OK, lfd = -1; 185 char biffmsg[100], buf[8*1024], path[MAXPATHLEN], lpath[MAXPATHLEN]; 186 off_t curoff; 187 188 /* 189 * Disallow delivery to unknown names -- special mailboxes can be 190 * handled in the sendmail aliases file. 191 */ 192 if ((getpwnam_r(name, &pwres, pwbuf, sizeof(pwbuf), &pw)) != 0) { 193 logwarn("unable to find user %s: %s", name, strerror(errno)); 194 return(EX_TEMPFAIL); 195 } 196 if (pw == NULL) { 197 logwarn("unknown name: %s", name); 198 return(EX_NOUSER); 199 } 200 201 (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name); 202 203 if (lockfile) { 204 (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", 205 _PATH_MAILDIR, name); 206 207 if((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL, 208 S_IRUSR|S_IWUSR)) < 0) { 209 logwarn("%s: %s", lpath, strerror(errno)); 210 return(EX_OSERR); 211 } 212 } 213 214 if ((lstat(path, &sb) != -1) && 215 (sb.st_nlink != 1 || S_ISLNK(sb.st_mode))) { 216 logwarn("%s: linked file", path); 217 return(EX_OSERR); 218 } 219 220 if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK|O_NOFOLLOW, 221 S_IRUSR|S_IWUSR)) == -1) { 222 /* create file */ 223 if (errno != ENOENT || 224 (mbfd = open(path, O_APPEND|O_CREAT|O_WRONLY|O_EXLOCK|O_EXCL, 225 S_IRUSR|S_IWUSR)) == -1) { 226 logwarn("%s: %s", path, strerror(errno)); 227 rval = EX_OSERR; 228 goto bad; 229 } 230 created = 1; 231 } else { 232 /* opened existing file, check for TOCTTOU */ 233 if (fstat(mbfd, &nsb) == -1) { 234 rval = EX_OSERR; 235 goto bad; 236 } 237 238 /* file is not what we expected */ 239 if (nsb.st_ino != sb.st_ino || nsb.st_dev != sb.st_dev) { 240 rval = EX_OSERR; 241 goto bad; 242 } 243 } 244 245 if ((curoff = lseek(mbfd, 0, SEEK_END)) == (off_t)-1) { 246 logwarn("%s: %s", path, strerror(errno)); 247 rval = EX_OSERR; 248 goto bad; 249 } 250 251 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, 252 (long long)curoff); 253 if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { 254 logwarn("temporary file: %s", strerror(errno)); 255 rval = EX_OSERR; 256 goto bad; 257 } 258 259 while ((nr = read(fd, buf, sizeof(buf))) > 0) 260 for (off = 0; off < nr; off += nw) 261 if ((nw = write(mbfd, buf + off, nr - off)) < 0) { 262 logwarn("%s: %s", path, strerror(errno)); 263 goto trunc; 264 } 265 if (nr < 0) { 266 logwarn("temporary file: %s", strerror(errno)); 267 trunc: (void)ftruncate(mbfd, curoff); 268 rval = EX_OSERR; 269 } 270 271 /* 272 * Set the owner and group. Historically, binmail repeated this at 273 * each mail delivery. We no longer do this, assuming that if the 274 * ownership or permissions were changed there was a reason for doing 275 * so. 276 */ 277 bad: 278 if (lockfile) { 279 if (lfd >= 0) { 280 unlink(lpath); 281 close(lfd); 282 } 283 } 284 285 if (mbfd >= 0) { 286 if (created) 287 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 288 289 (void)fsync(mbfd); /* Don't wait for update. */ 290 (void)close(mbfd); /* Implicit unlock. */ 291 } 292 293 if (rval == EX_OK) 294 notifybiff(biffmsg); 295 296 return rval; 297 } 298 299 void 300 notifybiff(char *msg) 301 { 302 static struct sockaddr_in addr; 303 static int f = -1; 304 struct hostent *hp; 305 struct servent *sp; 306 int len; 307 308 if (!addr.sin_family) { 309 /* Be silent if biff service not available. */ 310 if (!(sp = getservbyname("biff", "udp"))) 311 return; 312 if (!(hp = gethostbyname("localhost"))) { 313 logwarn("localhost: %s", strerror(errno)); 314 return; 315 } 316 addr.sin_len = sizeof(struct sockaddr_in); 317 addr.sin_family = hp->h_addrtype; 318 addr.sin_port = sp->s_port; 319 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); 320 } 321 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 322 logwarn("socket: %s", strerror(errno)); 323 return; 324 } 325 len = strlen(msg) + 1; 326 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 327 != len) 328 logwarn("sendto biff: %s", strerror(errno)); 329 } 330 331 static void 332 usage(void) 333 { 334 logerr(EX_USAGE, "usage: mail.local [-l] [-f from] user ..."); 335 } 336 337 static void 338 logerr(int status, const char *fmt, ...) 339 { 340 va_list ap; 341 342 va_start(ap, fmt); 343 vsyslog(LOG_ERR, fmt, ap); 344 va_end(ap); 345 346 exit(status); 347 /* NOTREACHED */ 348 } 349 350 static void 351 logwarn(const char *fmt, ...) 352 { 353 va_list ap; 354 355 va_start(ap, fmt); 356 vsyslog(LOG_ERR, fmt, ap); 357 va_end(ap); 358 return; 359 } 360