1 /* $NetBSD: mail.local.c,v 1.28 2016/07/21 12:29:37 shm 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.28 2016/07/21 12:29:37 shm 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, 221 S_IRUSR|S_IWUSR)) == -1) { 222 /* create file */ 223 if ((mbfd = open(path, O_APPEND|O_CREAT|O_WRONLY|O_EXLOCK, 224 S_IRUSR|S_IWUSR)) == -1) { 225 logwarn("%s: %s", path, strerror(errno)); 226 rval = EX_OSERR; 227 goto bad; 228 } 229 created = 1; 230 } else { 231 /* opened existing file, check for TOCTTOU */ 232 if (fstat(mbfd, &nsb) == -1) { 233 rval = EX_OSERR; 234 goto bad; 235 } 236 237 /* file is not what we expected */ 238 if (nsb.st_ino != sb.st_ino || nsb.st_dev != sb.st_dev) { 239 rval = EX_OSERR; 240 goto bad; 241 } 242 } 243 244 if ((curoff = lseek(mbfd, 0, SEEK_END)) == (off_t)-1) { 245 logwarn("%s: %s", path, strerror(errno)); 246 rval = EX_OSERR; 247 goto bad; 248 } 249 250 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, 251 (long long)curoff); 252 if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { 253 logwarn("temporary file: %s", strerror(errno)); 254 rval = EX_OSERR; 255 goto bad; 256 } 257 258 while ((nr = read(fd, buf, sizeof(buf))) > 0) 259 for (off = 0; off < nr; off += nw) 260 if ((nw = write(mbfd, buf + off, nr - off)) < 0) { 261 logwarn("%s: %s", path, strerror(errno)); 262 goto trunc; 263 } 264 if (nr < 0) { 265 logwarn("temporary file: %s", strerror(errno)); 266 trunc: (void)ftruncate(mbfd, curoff); 267 rval = EX_OSERR; 268 } 269 270 /* 271 * Set the owner and group. Historically, binmail repeated this at 272 * each mail delivery. We no longer do this, assuming that if the 273 * ownership or permissions were changed there was a reason for doing 274 * so. 275 */ 276 bad: 277 if (lockfile) { 278 if (lfd >= 0) { 279 unlink(lpath); 280 close(lfd); 281 } 282 } 283 284 if (mbfd >= 0) { 285 if (created) 286 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 287 288 (void)fsync(mbfd); /* Don't wait for update. */ 289 (void)close(mbfd); /* Implicit unlock. */ 290 } 291 292 if (rval == EX_OK) 293 notifybiff(biffmsg); 294 295 return rval; 296 } 297 298 void 299 notifybiff(char *msg) 300 { 301 static struct sockaddr_in addr; 302 static int f = -1; 303 struct hostent *hp; 304 struct servent *sp; 305 int len; 306 307 if (!addr.sin_family) { 308 /* Be silent if biff service not available. */ 309 if (!(sp = getservbyname("biff", "udp"))) 310 return; 311 if (!(hp = gethostbyname("localhost"))) { 312 logwarn("localhost: %s", strerror(errno)); 313 return; 314 } 315 addr.sin_len = sizeof(struct sockaddr_in); 316 addr.sin_family = hp->h_addrtype; 317 addr.sin_port = sp->s_port; 318 memcpy(&addr.sin_addr, hp->h_addr, hp->h_length); 319 } 320 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 321 logwarn("socket: %s", strerror(errno)); 322 return; 323 } 324 len = strlen(msg) + 1; 325 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 326 != len) 327 logwarn("sendto biff: %s", strerror(errno)); 328 } 329 330 static void 331 usage(void) 332 { 333 logerr(EX_USAGE, "usage: mail.local [-l] [-f from] user ..."); 334 } 335 336 static void 337 logerr(int status, const char *fmt, ...) 338 { 339 va_list ap; 340 341 va_start(ap, fmt); 342 vsyslog(LOG_ERR, fmt, ap); 343 va_end(ap); 344 345 exit(status); 346 /* NOTREACHED */ 347 } 348 349 static void 350 logwarn(const char *fmt, ...) 351 { 352 va_list ap; 353 354 va_start(ap, fmt); 355 vsyslog(LOG_ERR, fmt, ap); 356 va_end(ap); 357 return; 358 } 359