1 /* $OpenBSD: mail.local.c,v 1.35 2015/12/12 20:09:28 mmcc 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 <netinet/in.h> 38 #include <syslog.h> 39 #include <fcntl.h> 40 #include <netdb.h> 41 #include <pwd.h> 42 #include <time.h> 43 #include <unistd.h> 44 #include <limits.h> 45 #include <errno.h> 46 #include <stdio.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include "pathnames.h" 50 #include "mail.local.h" 51 52 int 53 main(int argc, char *argv[]) 54 { 55 struct passwd *pw; 56 int ch, fd, eval, lockfile=1, holdme=0; 57 uid_t uid; 58 char *from; 59 60 openlog("mail.local", LOG_PERROR, LOG_MAIL); 61 62 from = NULL; 63 while ((ch = getopt(argc, argv, "lLdf:r:H")) != -1) 64 switch (ch) { 65 case 'd': /* backward compatible */ 66 break; 67 case 'f': 68 case 'r': /* backward compatible */ 69 if (from) 70 merr(FATAL, "multiple -f options"); 71 from = optarg; 72 break; 73 case 'l': 74 lockfile=1; 75 break; 76 case 'L': 77 lockfile=0; 78 break; 79 case 'H': 80 holdme=1; 81 break; 82 default: 83 usage(); 84 } 85 argc -= optind; 86 argv += optind; 87 88 /* Support -H flag for backwards compat */ 89 if (holdme) { 90 execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL); 91 merr(FATAL, "execl: lockspool: %s", strerror(errno)); 92 } else { 93 if (!*argv) 94 usage(); 95 if (geteuid() != 0) 96 merr(FATAL, "may only be run by the superuser"); 97 } 98 99 /* 100 * If from not specified, use the name from getlogin() if the 101 * uid matches, otherwise, use the name from the password file 102 * corresponding to the uid. 103 */ 104 uid = getuid(); 105 if (!from && (!(from = getlogin()) || 106 !(pw = getpwnam(from)) || pw->pw_uid != uid)) 107 from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 108 109 fd = storemail(from); 110 for (eval = 0; *argv; ++argv) 111 eval |= deliver(fd, *argv, lockfile); 112 exit(eval); 113 } 114 115 int 116 storemail(char *from) 117 { 118 FILE *fp = NULL; 119 time_t tval; 120 int fd, eline; 121 size_t len; 122 char *line, *tbuf; 123 124 if ((tbuf = strdup(_PATH_LOCTMP)) == NULL) 125 merr(FATAL, "unable to allocate memory"); 126 if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+"))) 127 merr(FATAL, "unable to open temporary file"); 128 (void)unlink(tbuf); 129 free(tbuf); 130 131 (void)time(&tval); 132 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 133 134 for (eline = 1, tbuf = NULL; (line = fgetln(stdin, &len));) { 135 /* We have to NUL-terminate the line since fgetln does not */ 136 if (line[len - 1] == '\n') 137 line[len - 1] = '\0'; 138 else { 139 /* No trailing newline, so alloc space and copy */ 140 if ((tbuf = malloc(len + 1)) == NULL) 141 merr(FATAL, "unable to allocate memory"); 142 memcpy(tbuf, line, len); 143 tbuf[len] = '\0'; 144 line = tbuf; 145 } 146 if (line[0] == '\0') 147 eline = 1; 148 else { 149 if (eline && line[0] == 'F' && len > 5 && 150 !memcmp(line, "From ", 5)) 151 (void)putc('>', fp); 152 eline = 0; 153 } 154 (void)fprintf(fp, "%s\n", line); 155 if (ferror(fp)) 156 break; 157 } 158 free(tbuf); 159 160 /* Output a newline; note, empty messages are allowed. */ 161 (void)putc('\n', fp); 162 (void)fflush(fp); 163 if (ferror(fp)) 164 merr(FATAL, "temporary file write error"); 165 return(fd); 166 } 167 168 int 169 deliver(int fd, char *name, int lockfile) 170 { 171 struct stat sb, fsb; 172 struct passwd *pw; 173 int mbfd=-1, rval=1, lfd=-1; 174 char biffmsg[100], buf[8*1024], path[PATH_MAX]; 175 off_t curoff; 176 size_t off; 177 ssize_t nr, nw; 178 179 /* 180 * Disallow delivery to unknown names -- special mailboxes can be 181 * handled in the sendmail aliases file. 182 */ 183 if (!(pw = getpwnam(name))) { 184 merr(NOTFATAL, "unknown name: %s", name); 185 return(1); 186 } 187 188 (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name); 189 190 if (lockfile) { 191 lfd = getlock(name, pw); 192 if (lfd == -1) 193 return (1); 194 } 195 196 /* after this point, always exit via bad to remove lockfile */ 197 retry: 198 if (lstat(path, &sb)) { 199 if (errno != ENOENT) { 200 merr(NOTFATAL, "%s: %s", path, strerror(errno)); 201 goto bad; 202 } 203 if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK, 204 S_IRUSR|S_IWUSR)) < 0) { 205 if (errno == EEXIST) { 206 /* file appeared since lstat */ 207 goto retry; 208 } else { 209 merr(NOTFATAL, "%s: %s", path, strerror(errno)); 210 goto bad; 211 } 212 } 213 /* 214 * Set the owner and group. Historically, binmail repeated 215 * this at each mail delivery. We no longer do this, assuming 216 * that if the ownership or permissions were changed there 217 * was a reason for doing so. 218 */ 219 if (fchown(mbfd, pw->pw_uid, pw->pw_gid) < 0) { 220 merr(NOTFATAL, "chown %u:%u: %s", 221 pw->pw_uid, pw->pw_gid, name); 222 goto bad; 223 } 224 } else { 225 if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) { 226 merr(NOTFATAL, "%s: linked or special file", path); 227 goto bad; 228 } 229 if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK, 230 S_IRUSR|S_IWUSR)) < 0) { 231 merr(NOTFATAL, "%s: %s", path, strerror(errno)); 232 goto bad; 233 } 234 if (fstat(mbfd, &fsb)) { 235 /* relating error to path may be bad style */ 236 merr(NOTFATAL, "%s: %s", path, strerror(errno)); 237 goto bad; 238 } 239 if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) { 240 merr(NOTFATAL, "%s: changed after open", path); 241 goto bad; 242 } 243 /* paranoia? */ 244 if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) { 245 merr(NOTFATAL, "%s: linked or special file", path); 246 goto bad; 247 } 248 } 249 250 curoff = lseek(mbfd, 0, SEEK_END); 251 (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, curoff); 252 if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { 253 merr(NOTFATAL, "temporary file: %s", strerror(errno)); 254 goto bad; 255 } 256 257 while ((nr = read(fd, buf, sizeof(buf))) > 0) 258 for (off = 0; off < nr; off += nw) 259 if ((nw = write(mbfd, buf + off, nr - off)) < 0) { 260 merr(NOTFATAL, "%s: %s", path, strerror(errno)); 261 (void)ftruncate(mbfd, curoff); 262 goto bad; 263 } 264 265 if (nr == 0) { 266 rval = 0; 267 } else { 268 (void)ftruncate(mbfd, curoff); 269 merr(FATAL, "temporary file: %s", strerror(errno)); 270 } 271 272 bad: 273 if (lfd != -1) { 274 rellock(); 275 close(lfd); 276 } 277 278 if (mbfd != -1) { 279 (void)fsync(mbfd); /* Don't wait for update. */ 280 (void)close(mbfd); /* Implicit unlock. */ 281 } 282 283 if (!rval) 284 notifybiff(biffmsg); 285 return(rval); 286 } 287 288 void 289 notifybiff(char *msg) 290 { 291 static struct addrinfo *res0; 292 struct addrinfo hints, *res; 293 static int f = -1; 294 size_t len; 295 int error; 296 297 if (res0 == NULL) { 298 memset(&hints, 0, sizeof(hints)); 299 hints.ai_family = PF_UNSPEC; 300 hints.ai_socktype = SOCK_DGRAM; 301 302 error = getaddrinfo("localhost", "biff", &hints, &res0); 303 if (error) { 304 /* Be silent if biff service not available. */ 305 if (error != EAI_SERVICE) { 306 merr(NOTFATAL, "localhost: %s", 307 gai_strerror(error)); 308 } 309 return; 310 } 311 } 312 313 if (f == -1) { 314 for (res = res0; res != NULL; res = res->ai_next) { 315 f = socket(res->ai_family, res->ai_socktype, 316 res->ai_protocol); 317 if (f != -1) 318 break; 319 } 320 } 321 if (f == -1) { 322 merr(NOTFATAL, "socket: %s", strerror(errno)); 323 return; 324 } 325 326 len = strlen(msg) + 1; /* XXX */ 327 if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len) 328 merr(NOTFATAL, "sendto biff: %s", strerror(errno)); 329 } 330 331 void 332 usage(void) 333 { 334 merr(FATAL, "usage: mail.local [-Ll] [-f from] user ..."); 335 } 336