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