1 /* 2 * Copyright (c) 2008-2014, Simon Schubert <2@0x2c.org>. 3 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 4 * 5 * This code is derived from software contributed to The DragonFly Project 6 * by Simon Schubert <2@0x2c.org>. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 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 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 3. Neither the name of The DragonFly Project nor the names of its 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific, prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include "dfcompat.h" 37 38 #include <sys/param.h> 39 #include <sys/types.h> 40 #include <sys/queue.h> 41 #include <sys/stat.h> 42 #include <sys/time.h> 43 #include <sys/wait.h> 44 45 #include <dirent.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <inttypes.h> 50 #include <paths.h> 51 #include <pwd.h> 52 #include <signal.h> 53 #include <stdarg.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <syslog.h> 58 #include <unistd.h> 59 60 #include "dma.h" 61 62 extern int yyparse(void); 63 static void deliver(struct qitem *); 64 65 struct aliases aliases = LIST_HEAD_INITIALIZER(aliases); 66 struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs); 67 struct authusers authusers = LIST_HEAD_INITIALIZER(authusers); 68 char username[USERNAME_SIZE]; 69 uid_t useruid; 70 const char *logident_base; 71 char errmsg[ERRMSG_SIZE]; 72 73 static int daemonize = 1; 74 static int doqueue = 0; 75 76 struct config config = { 77 .smarthost = NULL, 78 .port = 25, 79 .aliases = "/etc/aliases", 80 .spooldir = "/var/spool/dma", 81 .authpath = NULL, 82 .certfile = NULL, 83 .features = 0, 84 .mailname = NULL, 85 .masquerade_host = NULL, 86 .masquerade_user = NULL, 87 }; 88 89 90 static void 91 sighup_handler(int signo) 92 { 93 (void)signo; /* so that gcc doesn't complain */ 94 } 95 96 static char * 97 set_from(struct queue *queue, const char *osender) 98 { 99 const char *addr; 100 char *sender; 101 102 if (osender) { 103 addr = osender; 104 } else if (getenv("EMAIL") != NULL) { 105 addr = getenv("EMAIL"); 106 } else { 107 if (config.masquerade_user) 108 addr = config.masquerade_user; 109 else 110 addr = username; 111 } 112 113 if (!strchr(addr, '@')) { 114 const char *from_host = hostname(); 115 116 if (config.masquerade_host) 117 from_host = config.masquerade_host; 118 119 if (asprintf(&sender, "%s@%s", addr, from_host) <= 0) 120 return (NULL); 121 } else { 122 sender = strdup(addr); 123 if (sender == NULL) 124 return (NULL); 125 } 126 127 if (strchr(sender, '\n') != NULL) { 128 errno = EINVAL; 129 return (NULL); 130 } 131 132 queue->sender = sender; 133 return (sender); 134 } 135 136 static int 137 read_aliases(void) 138 { 139 yyin = fopen(config.aliases, "r"); 140 if (yyin == NULL) { 141 /* 142 * Non-existing aliases file is not a fatal error 143 */ 144 if (errno == ENOENT) 145 return (0); 146 /* Other problems are. */ 147 return (-1); 148 } 149 if (yyparse()) 150 return (-1); /* fatal error, probably malloc() */ 151 fclose(yyin); 152 return (0); 153 } 154 155 static int 156 do_alias(struct queue *queue, const char *addr) 157 { 158 struct alias *al; 159 struct stritem *sit; 160 int aliased = 0; 161 162 LIST_FOREACH(al, &aliases, next) { 163 if (strcmp(al->alias, addr) != 0) 164 continue; 165 SLIST_FOREACH(sit, &al->dests, next) { 166 if (add_recp(queue, sit->str, EXPAND_ADDR) != 0) 167 return (-1); 168 } 169 aliased = 1; 170 } 171 172 return (aliased); 173 } 174 175 int 176 add_recp(struct queue *queue, const char *str, int expand) 177 { 178 struct qitem *it, *tit; 179 struct passwd *pw; 180 char *host; 181 int aliased = 0; 182 183 it = calloc(1, sizeof(*it)); 184 if (it == NULL) 185 return (-1); 186 it->addr = strdup(str); 187 if (it->addr == NULL) 188 return (-1); 189 190 it->sender = queue->sender; 191 host = strrchr(it->addr, '@'); 192 if (host != NULL && 193 (strcmp(host + 1, hostname()) == 0 || 194 strcmp(host + 1, "localhost") == 0)) { 195 *host = 0; 196 } 197 LIST_FOREACH(tit, &queue->queue, next) { 198 /* weed out duplicate dests */ 199 if (strcmp(tit->addr, it->addr) == 0) { 200 free(it->addr); 201 free(it); 202 return (0); 203 } 204 } 205 LIST_INSERT_HEAD(&queue->queue, it, next); 206 207 /** 208 * Do local delivery if there is no @. 209 * Do not do local delivery when NULLCLIENT is set. 210 */ 211 if (strrchr(it->addr, '@') == NULL && (config.features & NULLCLIENT) == 0) { 212 it->remote = 0; 213 if (expand) { 214 aliased = do_alias(queue, it->addr); 215 if (!aliased && expand == EXPAND_WILDCARD) 216 aliased = do_alias(queue, "*"); 217 if (aliased < 0) 218 return (-1); 219 if (aliased) { 220 LIST_REMOVE(it, next); 221 } else { 222 /* Local destination, check */ 223 pw = getpwnam(it->addr); 224 if (pw == NULL) 225 goto out; 226 /* XXX read .forward */ 227 endpwent(); 228 } 229 } 230 } else { 231 it->remote = 1; 232 } 233 234 return (0); 235 236 out: 237 free(it->addr); 238 free(it); 239 return (-1); 240 } 241 242 static struct qitem * 243 go_background(struct queue *queue) 244 { 245 struct sigaction sa; 246 struct qitem *it; 247 pid_t pid; 248 249 if (daemonize && daemon(0, 0) != 0) { 250 syslog(LOG_ERR, "can not daemonize: %m"); 251 exit(1); 252 } 253 daemonize = 0; 254 255 bzero(&sa, sizeof(sa)); 256 sa.sa_handler = SIG_IGN; 257 sigaction(SIGCHLD, &sa, NULL); 258 259 LIST_FOREACH(it, &queue->queue, next) { 260 /* No need to fork for the last dest */ 261 if (LIST_NEXT(it, next) == NULL) 262 goto retit; 263 264 pid = fork(); 265 switch (pid) { 266 case -1: 267 syslog(LOG_ERR, "can not fork: %m"); 268 exit(1); 269 break; 270 271 case 0: 272 /* 273 * Child: 274 * 275 * return and deliver mail 276 */ 277 retit: 278 /* 279 * If necessary, acquire the queue and * mail files. 280 * If this fails, we probably were raced by another 281 * process. It is okay to be raced if we're supposed 282 * to flush the queue. 283 */ 284 setlogident("%s", it->queueid); 285 switch (acquirespool(it)) { 286 case 0: 287 break; 288 case 1: 289 if (doqueue) 290 exit(0); 291 syslog(LOG_WARNING, "could not lock queue file"); 292 exit(1); 293 default: 294 exit(1); 295 } 296 dropspool(queue, it); 297 return (it); 298 299 default: 300 /* 301 * Parent: 302 * 303 * fork next child 304 */ 305 break; 306 } 307 } 308 309 syslog(LOG_CRIT, "reached dead code"); 310 exit(1); 311 } 312 313 static void 314 deliver(struct qitem *it) 315 { 316 int error; 317 unsigned int backoff = MIN_RETRY, slept; 318 struct timeval now; 319 struct stat st; 320 321 snprintf(errmsg, sizeof(errmsg), "unknown bounce reason"); 322 323 retry: 324 syslog(LOG_INFO, "trying delivery"); 325 326 if (it->remote) 327 error = deliver_remote(it); 328 else 329 error = deliver_local(it); 330 331 switch (error) { 332 case 0: 333 delqueue(it); 334 syslog(LOG_INFO, "delivery successful"); 335 exit(0); 336 337 case 1: 338 if (stat(it->queuefn, &st) != 0) { 339 syslog(LOG_ERR, "lost queue file `%s'", it->queuefn); 340 exit(1); 341 } 342 if (gettimeofday(&now, NULL) == 0 && 343 (now.tv_sec - st.st_mtim.tv_sec > MAX_TIMEOUT)) { 344 snprintf(errmsg, sizeof(errmsg), 345 "Could not deliver for the last %d seconds. Giving up.", 346 MAX_TIMEOUT); 347 goto bounce; 348 } 349 for (slept = 0; slept < backoff;) { 350 slept += SLEEP_TIMEOUT - sleep(SLEEP_TIMEOUT); 351 if (flushqueue_since(slept)) { 352 backoff = MIN_RETRY; 353 goto retry; 354 } 355 } 356 if (slept >= backoff) { 357 /* pick the next backoff between [1.5, 2.5) times backoff */ 358 backoff = backoff + backoff / 2 + random() % backoff; 359 if (backoff > MAX_RETRY) 360 backoff = MAX_RETRY; 361 } 362 goto retry; 363 364 case -1: 365 default: 366 break; 367 } 368 369 bounce: 370 bounce(it, errmsg); 371 /* NOTREACHED */ 372 } 373 374 void 375 run_queue(struct queue *queue) 376 { 377 struct qitem *it; 378 379 if (LIST_EMPTY(&queue->queue)) 380 return; 381 382 it = go_background(queue); 383 deliver(it); 384 /* NOTREACHED */ 385 } 386 387 static void 388 show_queue(struct queue *queue) 389 { 390 struct qitem *it; 391 int locked = 0; /* XXX */ 392 393 if (LIST_EMPTY(&queue->queue)) { 394 printf("Mail queue is empty\n"); 395 return; 396 } 397 398 LIST_FOREACH(it, &queue->queue, next) { 399 printf("ID\t: %s%s\n" 400 "From\t: %s\n" 401 "To\t: %s\n", 402 it->queueid, 403 locked ? "*" : "", 404 it->sender, it->addr); 405 406 if (LIST_NEXT(it, next) != NULL) 407 printf("--\n"); 408 } 409 } 410 411 /* 412 * TODO: 413 * 414 * - alias processing 415 * - use group permissions 416 * - proper sysexit codes 417 */ 418 419 int 420 main(int argc, char **argv) 421 { 422 struct sigaction act; 423 char *sender = NULL; 424 struct queue queue; 425 int i, ch; 426 int nodot = 0, showq = 0, queue_only = 0; 427 int recp_from_header = 0; 428 429 set_username(); 430 431 /* 432 * We never run as root. If called by root, drop permissions 433 * to the mail user. 434 */ 435 if (geteuid() == 0 || getuid() == 0) { 436 struct passwd *pw; 437 438 errno = 0; 439 pw = getpwnam(DMA_ROOT_USER); 440 if (pw == NULL) { 441 if (errno == 0) 442 errx(1, "user '%s' not found", DMA_ROOT_USER); 443 else 444 err(1, "cannot drop root privileges"); 445 } 446 447 if (setuid(pw->pw_uid) != 0) 448 err(1, "cannot drop root privileges"); 449 450 if (geteuid() == 0 || getuid() == 0) 451 errx(1, "cannot drop root privileges"); 452 } 453 454 atexit(deltmp); 455 init_random(); 456 457 bzero(&queue, sizeof(queue)); 458 LIST_INIT(&queue.queue); 459 460 if (strcmp(argv[0], "mailq") == 0) { 461 argv++; argc--; 462 showq = 1; 463 if (argc != 0 && strcmp(argv[0], "-Ac") != 0) 464 errx(1, "invalid arguments"); 465 goto skipopts; 466 } else if (strcmp(argv[0], "newaliases") == 0) { 467 logident_base = "dma"; 468 setlogident(NULL); 469 470 if (read_aliases() != 0) 471 errx(1, "could not parse aliases file `%s'", config.aliases); 472 exit(0); 473 } else if (strcmp(argv[0], "hoststat") == 0 || 474 strcmp(argv[0], "purgestat") == 0) { 475 exit(0); 476 } 477 478 opterr = 0; 479 while ((ch = getopt(argc, argv, ":A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:tUV:vX:")) != -1) { 480 switch (ch) { 481 case 'A': 482 /* -AX is being ignored, except for -A{c,m} */ 483 if (optarg[0] == 'c' || optarg[0] == 'm') { 484 break; 485 } 486 /* FALLTHROUGH */ 487 case 'b': 488 /* -bX is being ignored, except for -bp */ 489 if (optarg[0] == 'p') { 490 showq = 1; 491 break; 492 } else if (optarg[0] == 'q') { 493 queue_only = 1; 494 break; 495 } 496 /* FALLTHROUGH */ 497 case 'D': 498 daemonize = 0; 499 break; 500 case 'L': 501 logident_base = optarg; 502 break; 503 case 'f': 504 case 'r': 505 sender = optarg; 506 break; 507 508 case 't': 509 recp_from_header = 1; 510 break; 511 512 case 'o': 513 /* -oX is being ignored, except for -oi */ 514 if (optarg[0] != 'i') 515 break; 516 /* FALLTHROUGH */ 517 case 'O': 518 break; 519 case 'i': 520 nodot = 1; 521 break; 522 523 case 'q': 524 /* Don't let getopt slup up other arguments */ 525 if (optarg && *optarg == '-') 526 optind--; 527 doqueue = 1; 528 break; 529 530 /* Ignored options */ 531 case 'B': 532 case 'C': 533 case 'd': 534 case 'F': 535 case 'h': 536 case 'N': 537 case 'n': 538 case 'R': 539 case 'U': 540 case 'V': 541 case 'v': 542 case 'X': 543 break; 544 545 case ':': 546 if (optopt == 'q') { 547 doqueue = 1; 548 break; 549 } 550 /* FALLTHROUGH */ 551 552 default: 553 fprintf(stderr, "invalid argument: `-%c'\n", optopt); 554 exit(1); 555 } 556 } 557 argc -= optind; 558 argv += optind; 559 opterr = 1; 560 561 if (argc != 0 && (showq || doqueue)) 562 errx(1, "sending mail and queue operations are mutually exclusive"); 563 564 if (showq + doqueue > 1) 565 errx(1, "conflicting queue operations"); 566 567 skipopts: 568 if (logident_base == NULL) 569 logident_base = "dma"; 570 setlogident(NULL); 571 572 act.sa_handler = sighup_handler; 573 act.sa_flags = 0; 574 sigemptyset(&act.sa_mask); 575 if (sigaction(SIGHUP, &act, NULL) != 0) 576 syslog(LOG_WARNING, "can not set signal handler: %m"); 577 578 parse_conf(CONF_PATH "/dma.conf"); 579 580 if (config.authpath != NULL) 581 parse_authfile(config.authpath); 582 583 if (showq) { 584 if (load_queue(&queue) < 0) 585 errlog(1, "can not load queue"); 586 show_queue(&queue); 587 return (0); 588 } 589 590 if (doqueue) { 591 flushqueue_signal(); 592 if (load_queue(&queue) < 0) 593 errlog(1, "can not load queue"); 594 run_queue(&queue); 595 return (0); 596 } 597 598 if (read_aliases() != 0) 599 errlog(1, "could not parse aliases file `%s'", config.aliases); 600 601 if ((sender = set_from(&queue, sender)) == NULL) 602 errlog(1, NULL); 603 604 if (newspoolf(&queue) != 0) 605 errlog(1, "can not create temp file in `%s'", config.spooldir); 606 607 setlogident("%s", queue.id); 608 609 for (i = 0; i < argc; i++) { 610 if (add_recp(&queue, argv[i], EXPAND_WILDCARD) != 0) 611 errlogx(1, "invalid recipient `%s'", argv[i]); 612 } 613 614 if (LIST_EMPTY(&queue.queue) && !recp_from_header) 615 errlogx(1, "no recipients"); 616 617 if (readmail(&queue, nodot, recp_from_header) != 0) 618 errlog(1, "can not read mail"); 619 620 if (LIST_EMPTY(&queue.queue)) 621 errlogx(1, "no recipients"); 622 623 if (linkspool(&queue) != 0) 624 errlog(1, "can not create spools"); 625 626 /* From here on the mail is safe. */ 627 628 if (config.features & DEFER || queue_only) 629 return (0); 630 631 run_queue(&queue); 632 633 /* NOTREACHED */ 634 return (0); 635 } 636