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