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