dma.c revision 304588
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
63static void deliver(struct qitem *);
64
65struct aliases aliases = LIST_HEAD_INITIALIZER(aliases);
66struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs);
67struct authusers authusers = LIST_HEAD_INITIALIZER(authusers);
68char username[USERNAME_SIZE];
69uid_t useruid;
70const char *logident_base;
71char errmsg[ERRMSG_SIZE];
72
73static int daemonize = 1;
74static int doqueue = 0;
75
76struct 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
90static void
91sighup_handler(int signo)
92{
93	(void)signo;	/* so that gcc doesn't complain */
94}
95
96static char *
97set_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
136static int
137read_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
155static int
156do_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
175int
176add_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
236out:
237	free(it->addr);
238	free(it);
239	return (-1);
240}
241
242static struct qitem *
243go_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			 */
277retit:
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
313static void
314deliver(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
323retry:
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
369bounce:
370	bounce(it, errmsg);
371	/* NOTREACHED */
372}
373
374void
375run_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
387static void
388show_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
419int
420main(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
564skipopts:
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