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 = MIN_RETRY; 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 *= 2; 306 if (backoff > MAX_RETRY) 307 backoff = MAX_RETRY; 308 goto retry; 309 310 case -1: 311 default: 312 break; 313 } 314 315 bounce: 316 bounce(it, errmsg); 317 /* NOTREACHED */ 318 } 319 320 void 321 run_queue(struct queue *queue) 322 { 323 struct qitem *it; 324 325 if (LIST_EMPTY(&queue->queue)) 326 return; 327 328 it = go_background(queue); 329 deliver(it); 330 /* NOTREACHED */ 331 } 332 333 static void 334 show_queue(struct queue *queue) 335 { 336 struct qitem *it; 337 int locked = 0; /* XXX */ 338 339 if (LIST_EMPTY(&queue->queue)) { 340 printf("Mail queue is empty\n"); 341 return; 342 } 343 344 LIST_FOREACH(it, &queue->queue, next) { 345 printf("ID\t: %s%s\n" 346 "From\t: %s\n" 347 "To\t: %s\n", 348 it->queueid, 349 locked ? "*" : "", 350 it->sender, it->addr); 351 352 if (LIST_NEXT(it, next) != NULL) 353 printf("--\n"); 354 } 355 } 356 357 /* 358 * TODO: 359 * 360 * - alias processing 361 * - use group permissions 362 * - proper sysexit codes 363 */ 364 365 int 366 main(int argc, char **argv) 367 { 368 char *sender = NULL; 369 struct queue queue; 370 int i, ch; 371 int nodot = 0, doqueue = 0, showq = 0, queue_only = 0; 372 int recp_from_header = 0; 373 374 atexit(deltmp); 375 376 bzero(&queue, sizeof(queue)); 377 LIST_INIT(&queue.queue); 378 379 if (strcmp(argv[0], "mailq") == 0) { 380 argv++; argc--; 381 showq = 1; 382 if (argc != 0) 383 errx(1, "invalid arguments"); 384 goto skipopts; 385 } 386 387 opterr = 0; 388 while ((ch = getopt(argc, argv, ":A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:tUV:vX:")) != -1) { 389 switch (ch) { 390 case 'A': 391 /* -AX is being ignored, except for -A{c,m} */ 392 if (optarg[0] == 'c' || optarg[0] == 'm') { 393 break; 394 } 395 /* else FALLTRHOUGH */ 396 case 'b': 397 /* -bX is being ignored, except for -bp */ 398 if (optarg[0] == 'p') { 399 showq = 1; 400 break; 401 } else if (optarg[0] == 'q') { 402 queue_only = 1; 403 break; 404 } 405 /* else FALLTRHOUGH */ 406 case 'D': 407 daemonize = 0; 408 break; 409 case 'L': 410 logident_base = optarg; 411 break; 412 case 'f': 413 case 'r': 414 sender = optarg; 415 break; 416 417 case 't': 418 recp_from_header = 1; 419 break; 420 421 case 'o': 422 /* -oX is being ignored, except for -oi */ 423 if (optarg[0] != 'i') 424 break; 425 /* else FALLTRHOUGH */ 426 case 'O': 427 break; 428 case 'i': 429 nodot = 1; 430 break; 431 432 case 'q': 433 doqueue = 1; 434 break; 435 436 /* Ignored options */ 437 case 'B': 438 case 'C': 439 case 'd': 440 case 'F': 441 case 'h': 442 case 'N': 443 case 'n': 444 case 'R': 445 case 'U': 446 case 'V': 447 case 'v': 448 case 'X': 449 break; 450 451 case ':': 452 if (optopt == 'q') { 453 doqueue = 1; 454 break; 455 } 456 /* FALLTHROUGH */ 457 458 default: 459 fprintf(stderr, "invalid argument: `-%c'\n", optopt); 460 exit(1); 461 } 462 } 463 argc -= optind; 464 argv += optind; 465 opterr = 1; 466 467 if (argc != 0 && (showq || doqueue)) 468 errx(1, "sending mail and queue operations are mutually exclusive"); 469 470 if (showq + doqueue > 1) 471 errx(1, "conflicting queue operations"); 472 473 skipopts: 474 if (logident_base == NULL) 475 logident_base = "dma"; 476 setlogident(NULL); 477 set_username(); 478 479 /* XXX fork root here */ 480 481 parse_conf(CONF_PATH); 482 483 if (config.features & VIRTUAL) { 484 if (config.virtualpath == NULL) 485 errlogx(1, "no virtuser file specified, but VIRTUAL configured"); 486 parse_virtuser(config.virtualpath); 487 } 488 489 if (config.authpath != NULL) 490 parse_authfile(config.authpath); 491 492 if (showq) { 493 if (load_queue(&queue) < 0) 494 errlog(1, "can not load queue"); 495 show_queue(&queue); 496 return (0); 497 } 498 499 if (doqueue) { 500 if (load_queue(&queue) < 0) 501 errlog(1, "can not load queue"); 502 run_queue(&queue); 503 return (0); 504 } 505 506 if (read_aliases() != 0) 507 errlog(1, "can not read aliases file `%s'", config.aliases); 508 509 if ((sender = set_from(&queue, sender)) == NULL) 510 errlog(1, NULL); 511 512 if (newspoolf(&queue) != 0) 513 errlog(1, "can not create temp file"); 514 515 setlogident("%s", queue.id); 516 517 for (i = 0; i < argc; i++) { 518 if (add_recp(&queue, argv[i], 1) != 0) 519 errlogx(1, "invalid recipient `%s'", argv[i]); 520 } 521 522 if (LIST_EMPTY(&queue.queue) && !recp_from_header) 523 errlogx(1, "no recipients"); 524 525 if (readmail(&queue, nodot, recp_from_header) != 0) 526 errlog(1, "can not read mail"); 527 528 if (LIST_EMPTY(&queue.queue)) 529 errlogx(1, "no recipients"); 530 531 if (linkspool(&queue) != 0) 532 errlog(1, "can not create spools"); 533 534 /* From here on the mail is safe. */ 535 536 if (config.features & DEFER || queue_only) 537 return (0); 538 539 run_queue(&queue); 540 541 /* NOTREACHED */ 542 return (0); 543 } 544