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/queue.h> 37 #include <sys/stat.h> 38 #include <sys/types.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 60 static void deliver(struct qitem *); 61 62 struct aliases aliases = LIST_HEAD_INITIALIZER(aliases); 63 struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs); 64 struct virtusers virtusers = LIST_HEAD_INITIALIZER(virtusers); 65 struct authusers authusers = LIST_HEAD_INITIALIZER(authusers); 66 struct config *config; 67 const char *username; 68 const char *logident_base; 69 70 static int daemonize = 1; 71 72 static char * 73 set_from(struct queue *queue, const char *osender) 74 { 75 struct virtuser *v; 76 char *sender; 77 78 if ((config->features & VIRTUAL) != 0) { 79 SLIST_FOREACH(v, &virtusers, next) { 80 if (strcmp(v->login, username) == 0) { 81 sender = strdup(v->address); 82 if (sender == NULL) 83 return(NULL); 84 goto out; 85 } 86 } 87 } 88 89 if (osender) { 90 sender = strdup(osender); 91 if (sender == NULL) 92 return (NULL); 93 } else { 94 if (asprintf(&sender, "%s@%s", username, hostname()) <= 0) 95 return (NULL); 96 } 97 98 if (strchr(sender, '\n') != NULL) { 99 errno = EINVAL; 100 return (NULL); 101 } 102 103 out: 104 queue->sender = sender; 105 return (sender); 106 } 107 108 static int 109 read_aliases(void) 110 { 111 yyin = fopen(config->aliases, "r"); 112 if (yyin == NULL) 113 return (0); /* not fatal */ 114 if (yyparse()) 115 return (-1); /* fatal error, probably malloc() */ 116 fclose(yyin); 117 return (0); 118 } 119 120 int 121 add_recp(struct queue *queue, const char *str, int expand) 122 { 123 struct qitem *it, *tit; 124 struct stritem *sit; 125 struct alias *al; 126 struct passwd *pw; 127 char *host; 128 int aliased = 0; 129 130 it = calloc(1, sizeof(*it)); 131 if (it == NULL) 132 return (-1); 133 it->addr = strdup(str); 134 if (it->addr == NULL) 135 return (-1); 136 137 it->sender = queue->sender; 138 host = strrchr(it->addr, '@'); 139 if (host != NULL && 140 (strcmp(host + 1, hostname()) == 0 || 141 strcmp(host + 1, "localhost") == 0)) { 142 *host = 0; 143 } 144 LIST_FOREACH(tit, &queue->queue, next) { 145 /* weed out duplicate dests */ 146 if (strcmp(tit->addr, it->addr) == 0) { 147 free(it->addr); 148 free(it); 149 return (0); 150 } 151 } 152 LIST_INSERT_HEAD(&queue->queue, it, next); 153 if (strrchr(it->addr, '@') == NULL) { 154 it->remote = 0; 155 if (expand) { 156 LIST_FOREACH(al, &aliases, next) { 157 if (strcmp(al->alias, it->addr) != 0) 158 continue; 159 SLIST_FOREACH(sit, &al->dests, next) { 160 if (add_recp(queue, sit->str, 1) != 0) 161 return (-1); 162 } 163 aliased = 1; 164 } 165 if (aliased) { 166 LIST_REMOVE(it, next); 167 } else { 168 /* Local destination, check */ 169 pw = getpwnam(it->addr); 170 if (pw == NULL) 171 goto out; 172 /* XXX read .forward */ 173 endpwent(); 174 } 175 } 176 } else { 177 it->remote = 1; 178 } 179 180 return (0); 181 182 out: 183 free(it->addr); 184 free(it); 185 return (-1); 186 } 187 188 static struct qitem * 189 go_background(struct queue *queue) 190 { 191 struct sigaction sa; 192 struct qitem *it; 193 pid_t pid; 194 195 if (daemonize && daemon(0, 0) != 0) { 196 syslog(LOG_ERR, "can not daemonize: %m"); 197 exit(1); 198 } 199 daemonize = 0; 200 201 bzero(&sa, sizeof(sa)); 202 sa.sa_flags = SA_NOCLDWAIT; 203 sa.sa_handler = SIG_IGN; 204 sigaction(SIGCHLD, &sa, NULL); 205 206 LIST_FOREACH(it, &queue->queue, next) { 207 /* No need to fork for the last dest */ 208 if (LIST_NEXT(it, next) == NULL) 209 goto retit; 210 211 pid = fork(); 212 switch (pid) { 213 case -1: 214 syslog(LOG_ERR, "can not fork: %m"); 215 exit(1); 216 break; 217 218 case 0: 219 /* 220 * Child: 221 * 222 * return and deliver mail 223 */ 224 retit: 225 /* 226 * If necessary, acquire the queue and * mail files. 227 * If this fails, we probably were raced by another 228 * process. 229 */ 230 setlogident("%s", it->queueid); 231 if (acquirespool(it) < 0) 232 exit(1); 233 dropspool(queue, it); 234 return (it); 235 236 default: 237 /* 238 * Parent: 239 * 240 * fork next child 241 */ 242 break; 243 } 244 } 245 246 syslog(LOG_CRIT, "reached dead code"); 247 exit(1); 248 } 249 250 static void 251 deliver(struct qitem *it) 252 { 253 int error; 254 unsigned int backoff = MIN_RETRY; 255 const char *errmsg = "unknown bounce reason"; 256 struct timeval now; 257 struct stat st; 258 259 retry: 260 syslog(LOG_INFO, "trying delivery"); 261 262 if (it->remote) 263 error = deliver_remote(it, &errmsg); 264 else 265 error = deliver_local(it, &errmsg); 266 267 switch (error) { 268 case 0: 269 delqueue(it); 270 syslog(LOG_INFO, "delivery successful"); 271 exit(0); 272 273 case 1: 274 if (stat(it->queuefn, &st) != 0) { 275 syslog(LOG_ERR, "lost queue file `%s'", it->queuefn); 276 exit(1); 277 } 278 if (gettimeofday(&now, NULL) == 0 && 279 (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) { 280 asprintf(__DECONST(void *, &errmsg), 281 "Could not deliver for the last %d seconds. Giving up.", 282 MAX_TIMEOUT); 283 goto bounce; 284 } 285 sleep(backoff); 286 backoff *= 2; 287 if (backoff > MAX_RETRY) 288 backoff = MAX_RETRY; 289 goto retry; 290 291 case -1: 292 default: 293 break; 294 } 295 296 bounce: 297 bounce(it, errmsg); 298 /* NOTREACHED */ 299 } 300 301 void 302 run_queue(struct queue *queue) 303 { 304 struct qitem *it; 305 306 if (LIST_EMPTY(&queue->queue)) 307 return; 308 309 it = go_background(queue); 310 deliver(it); 311 /* NOTREACHED */ 312 } 313 314 static void 315 show_queue(struct queue *queue) 316 { 317 struct qitem *it; 318 int locked = 0; /* XXX */ 319 320 if (LIST_EMPTY(&queue->queue)) { 321 printf("Mail queue is empty\n"); 322 return; 323 } 324 325 LIST_FOREACH(it, &queue->queue, next) { 326 printf("ID\t: %s%s\n" 327 "From\t: %s\n" 328 "To\t: %s\n", 329 it->queueid, 330 locked ? "*" : "", 331 it->sender, it->addr); 332 333 if (LIST_NEXT(it, next) != NULL) 334 printf("--\n"); 335 } 336 } 337 338 /* 339 * TODO: 340 * 341 * - alias processing 342 * - use group permissions 343 * - proper sysexit codes 344 */ 345 346 int 347 main(int argc, char **argv) 348 { 349 char *sender = NULL; 350 struct queue queue; 351 int i, ch; 352 int nodot = 0, doqueue = 0, showq = 0, queue_only = 0; 353 int recp_from_header = 0; 354 355 atexit(deltmp); 356 357 bzero(&queue, sizeof(queue)); 358 LIST_INIT(&queue.queue); 359 360 if (strcmp(argv[0], "mailq") == 0) { 361 argv++; argc--; 362 showq = 1; 363 if (argc != 0) 364 errx(1, "invalid arguments"); 365 goto skipopts; 366 } 367 368 opterr = 0; 369 while ((ch = getopt(argc, argv, ":A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:tUV:vX:")) != -1) { 370 switch (ch) { 371 case 'A': 372 /* -AX is being ignored, except for -A{c,m} */ 373 if (optarg[0] == 'c' || optarg[0] == 'm') { 374 break; 375 } 376 /* else FALLTRHOUGH */ 377 case 'b': 378 /* -bX is being ignored, except for -bp */ 379 if (optarg[0] == 'p') { 380 showq = 1; 381 break; 382 } else if (optarg[0] == 'q') { 383 queue_only = 1; 384 break; 385 } 386 /* else FALLTRHOUGH */ 387 case 'D': 388 daemonize = 0; 389 break; 390 case 'L': 391 logident_base = optarg; 392 break; 393 case 'f': 394 case 'r': 395 sender = optarg; 396 break; 397 398 case 't': 399 recp_from_header = 1; 400 break; 401 402 case 'o': 403 /* -oX is being ignored, except for -oi */ 404 if (optarg[0] != 'i') 405 break; 406 /* else FALLTRHOUGH */ 407 case 'O': 408 break; 409 case 'i': 410 nodot = 1; 411 break; 412 413 case 'q': 414 doqueue = 1; 415 break; 416 417 /* Ignored options */ 418 case 'B': 419 case 'C': 420 case 'd': 421 case 'F': 422 case 'h': 423 case 'N': 424 case 'n': 425 case 'R': 426 case 'U': 427 case 'V': 428 case 'v': 429 case 'X': 430 break; 431 432 case ':': 433 if (optopt == 'q') { 434 doqueue = 1; 435 break; 436 } 437 /* FALLTHROUGH */ 438 439 default: 440 fprintf(stderr, "invalid argument: `-%c'\n", optopt); 441 exit(1); 442 } 443 } 444 argc -= optind; 445 argv += optind; 446 opterr = 1; 447 448 if (argc != 0 && (showq || doqueue)) 449 errx(1, "sending mail and queue operations are mutually exclusive"); 450 451 if (showq + doqueue > 1) 452 errx(1, "conflicting queue operations"); 453 454 skipopts: 455 if (logident_base == NULL) 456 logident_base = "dma"; 457 setlogident(NULL); 458 set_username(); 459 460 /* XXX fork root here */ 461 462 config = calloc(1, sizeof(*config)); 463 if (config == NULL) 464 errlog(1, NULL); 465 466 if (parse_conf(CONF_PATH) < 0) { 467 free(config); 468 errlog(1, "can not read config file"); 469 } 470 471 if (config->features & VIRTUAL) 472 if (parse_virtuser(config->virtualpath) < 0) 473 errlog(1, "can not read virtual user file `%s'", 474 config->virtualpath); 475 476 if (parse_authfile(config->authpath) < 0) 477 errlog(1, "can not read SMTP authentication file"); 478 479 if (showq) { 480 if (load_queue(&queue) < 0) 481 errlog(1, "can not load queue"); 482 show_queue(&queue); 483 return (0); 484 } 485 486 if (doqueue) { 487 if (load_queue(&queue) < 0) 488 errlog(1, "can not load queue"); 489 run_queue(&queue); 490 return (0); 491 } 492 493 if (read_aliases() != 0) 494 errlog(1, "can not read aliases file `%s'", config->aliases); 495 496 if ((sender = set_from(&queue, sender)) == NULL) 497 errlog(1, NULL); 498 499 if (newspoolf(&queue) != 0) 500 errlog(1, "can not create temp file"); 501 502 setlogident("%s", queue.id); 503 504 for (i = 0; i < argc; i++) { 505 if (add_recp(&queue, argv[i], 1) != 0) 506 errlogx(1, "invalid recipient `%s'", argv[i]); 507 } 508 509 if (LIST_EMPTY(&queue.queue) && !recp_from_header) 510 errlogx(1, "no recipients"); 511 512 if (readmail(&queue, nodot, recp_from_header) != 0) 513 errlog(1, "can not read mail"); 514 515 if (LIST_EMPTY(&queue.queue)) 516 errlogx(1, "no recipients"); 517 518 if (linkspool(&queue) != 0) 519 errlog(1, "can not create spools"); 520 521 /* From here on the mail is safe. */ 522 523 if (config->features & DEFER || queue_only) 524 return (0); 525 526 run_queue(&queue); 527 528 /* NOTREACHED */ 529 return (0); 530 } 531