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