xref: /openbsd-src/usr.sbin/smtpd/control.c (revision bf921b2a265b968a018032bb165729419e711549)
1*bf921b2aSclaudio /*	$OpenBSD: control.c,v 1.132 2024/11/21 13:42:22 claudio Exp $	*/
21f3c2bcfSsobrado 
33ef9cbf7Sgilles /*
465c4fdfbSgilles  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
53ef9cbf7Sgilles  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
63ef9cbf7Sgilles  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
73ef9cbf7Sgilles  *
83ef9cbf7Sgilles  * Permission to use, copy, modify, and distribute this software for any
93ef9cbf7Sgilles  * purpose with or without fee is hereby granted, provided that the above
103ef9cbf7Sgilles  * copyright notice and this permission notice appear in all copies.
113ef9cbf7Sgilles  *
123ef9cbf7Sgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
133ef9cbf7Sgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
143ef9cbf7Sgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
153ef9cbf7Sgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
163ef9cbf7Sgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
173ef9cbf7Sgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
183ef9cbf7Sgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
193ef9cbf7Sgilles  */
203ef9cbf7Sgilles 
213ef9cbf7Sgilles #include <sys/stat.h>
223ef9cbf7Sgilles #include <sys/un.h>
233ef9cbf7Sgilles 
243ef9cbf7Sgilles #include <errno.h>
253ef9cbf7Sgilles #include <pwd.h>
26439d0b42Schl #include <signal.h>
273ef9cbf7Sgilles #include <stdlib.h>
283ef9cbf7Sgilles #include <string.h>
290dcffd0dSop #include <time.h>
303ef9cbf7Sgilles #include <unistd.h>
313ef9cbf7Sgilles 
323ef9cbf7Sgilles #include "smtpd.h"
335eb8dddaSgilles #include "log.h"
343ef9cbf7Sgilles 
353ef9cbf7Sgilles #define CONTROL_BACKLOG 5
363ef9cbf7Sgilles 
37104d47d3Seric struct ctl_conn {
3865c4fdfbSgilles 	uint32_t		 id;
39104d47d3Seric 	uint8_t			 flags;
40104d47d3Seric #define CTL_CONN_NOTIFY		 0x01
4165c4fdfbSgilles 	struct mproc		 mproc;
4265c4fdfbSgilles 	uid_t			 euid;
4365c4fdfbSgilles 	gid_t			 egid;
44104d47d3Seric };
45104d47d3Seric 
463ef9cbf7Sgilles struct {
473ef9cbf7Sgilles 	struct event		 ev;
483ef9cbf7Sgilles 	int			 fd;
493ef9cbf7Sgilles } control_state;
503ef9cbf7Sgilles 
5165c4fdfbSgilles static void control_imsg(struct mproc *, struct imsg *);
52104d47d3Seric static void control_shutdown(void);
53104d47d3Seric static void control_listen(void);
54104d47d3Seric static void control_accept(int, short, void *);
55104d47d3Seric static void control_close(struct ctl_conn *);
5665c4fdfbSgilles static void control_dispatch_ext(struct mproc *, struct imsg *);
5782614934Seric static void control_digest_update(const char *, size_t, int);
5857d312f7Seric static void control_broadcast_verbose(int, int);
5982614934Seric 
609ed3223cSgilles static struct stat_backend *stat_backend = NULL;
619ed3223cSgilles extern const char *backend_stat;
629ed3223cSgilles 
63bf1fa702Sgilles static uint64_t			connid = 0;
6465c4fdfbSgilles static struct tree		ctl_conns;
658e9397b5Sgilles static struct tree		ctl_count;
6699399201Smartijn static struct stat_digest	digest;
6782614934Seric 
6851474229Sgilles #define	CONTROL_FD_RESERVE		5
698e9397b5Sgilles #define	CONTROL_MAXCONN_PER_CLIENT	32
709ed3223cSgilles 
71104d47d3Seric static void
7265c4fdfbSgilles control_imsg(struct mproc *p, struct imsg *imsg)
73ed1929b6Sjacekm {
74ed1929b6Sjacekm 	struct ctl_conn		*c;
75e4632cf4Sgilles 	struct stat_value	 val;
7665c4fdfbSgilles 	struct msg		 m;
7765c4fdfbSgilles 	const char		*key;
7865c4fdfbSgilles 	const void		*data;
7965c4fdfbSgilles 	size_t			 sz;
80ed1929b6Sjacekm 
8143962b9cSeric 	if (imsg == NULL) {
8243962b9cSeric 		if (p->proc != PROC_CLIENT)
8343962b9cSeric 			control_shutdown();
8443962b9cSeric 		return;
8543962b9cSeric 	}
8643962b9cSeric 
874fe02f32Seric 	switch (imsg->hdr.type) {
880fcb81a3Seric 	case IMSG_CTL_OK:
890fcb81a3Seric 	case IMSG_CTL_FAIL:
9065c4fdfbSgilles 	case IMSG_CTL_LIST_MESSAGES:
9165c4fdfbSgilles 	case IMSG_CTL_LIST_ENVELOPES:
92a9835440Ssunil 	case IMSG_CTL_DISCOVER_EVPID:
93a9835440Ssunil 	case IMSG_CTL_DISCOVER_MSGID:
94f9a337f4Seric 	case IMSG_CTL_MTA_SHOW_HOSTS:
95f9a337f4Seric 	case IMSG_CTL_MTA_SHOW_RELAYS:
96c5acbec8Seric 	case IMSG_CTL_MTA_SHOW_ROUTES:
97c5acbec8Seric 	case IMSG_CTL_MTA_SHOW_HOSTSTATS:
985b6a9ce9Seric 	case IMSG_CTL_MTA_SHOW_BLOCK:
99c5acbec8Seric 		c = tree_get(&ctl_conns, imsg->hdr.peerid);
100c5acbec8Seric 		if (c == NULL)
101c5acbec8Seric 			return;
1020fcb81a3Seric 		imsg->hdr.peerid = 0;
103c5acbec8Seric 		m_forward(&c->mproc, imsg);
104c5acbec8Seric 		return;
105ed1929b6Sjacekm 
10625d8b68dSeric 	case IMSG_CTL_SMTP_SESSION:
10725d8b68dSeric 		c = tree_get(&ctl_conns, imsg->hdr.peerid);
10825d8b68dSeric 		if (c == NULL)
10925d8b68dSeric 			return;
110510586acSclaudio 		m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg_get_fd(imsg),
111510586acSclaudio 		    NULL, 0);
11225d8b68dSeric 		return;
11325d8b68dSeric 
1149ed3223cSgilles 	case IMSG_STAT_INCREMENT:
11565c4fdfbSgilles 		m_msg(&m, imsg);
11665c4fdfbSgilles 		m_get_string(&m, &key);
11765c4fdfbSgilles 		m_get_data(&m, &data, &sz);
11865c4fdfbSgilles 		m_end(&m);
119bc1ed85bSsunil 		if (sz != sizeof(val))
120bc1ed85bSsunil 			fatalx("control: IMSG_STAT_INCREMENT size mismatch");
12165c4fdfbSgilles 		memmove(&val, data, sz);
1229ed3223cSgilles 		if (stat_backend)
123e4632cf4Sgilles 			stat_backend->increment(key, val.u.counter);
12482614934Seric 		control_digest_update(key, val.u.counter, 1);
1259ed3223cSgilles 		return;
12625d8b68dSeric 
1279ed3223cSgilles 	case IMSG_STAT_DECREMENT:
12865c4fdfbSgilles 		m_msg(&m, imsg);
12965c4fdfbSgilles 		m_get_string(&m, &key);
13065c4fdfbSgilles 		m_get_data(&m, &data, &sz);
13165c4fdfbSgilles 		m_end(&m);
132bc1ed85bSsunil 		if (sz != sizeof(val))
133bc1ed85bSsunil 			fatalx("control: IMSG_STAT_DECREMENT size mismatch");
13465c4fdfbSgilles 		memmove(&val, data, sz);
1359ed3223cSgilles 		if (stat_backend)
136e4632cf4Sgilles 			stat_backend->decrement(key, val.u.counter);
13782614934Seric 		control_digest_update(key, val.u.counter, 0);
1389ed3223cSgilles 		return;
13925d8b68dSeric 
1409ed3223cSgilles 	case IMSG_STAT_SET:
14165c4fdfbSgilles 		m_msg(&m, imsg);
14265c4fdfbSgilles 		m_get_string(&m, &key);
14365c4fdfbSgilles 		m_get_data(&m, &data, &sz);
14465c4fdfbSgilles 		m_end(&m);
145bc1ed85bSsunil 		if (sz != sizeof(val))
146bc1ed85bSsunil 			fatalx("control: IMSG_STAT_SET size mismatch");
14765c4fdfbSgilles 		memmove(&val, data, sz);
1489ed3223cSgilles 		if (stat_backend)
149e4632cf4Sgilles 			stat_backend->set(key, &val);
1509ed3223cSgilles 		return;
1519ed3223cSgilles 	}
1529ed3223cSgilles 
153ff01b044Seric 	fatalx("control_imsg: unexpected %s imsg",
1546d095224Schl 	    imsg_to_str(imsg->hdr.type));
155ed1929b6Sjacekm }
156ed1929b6Sjacekm 
1575894db6eSeric int
1585894db6eSeric control_create_socket(void)
1593ef9cbf7Sgilles {
160ac61da4aSgilles 	struct sockaddr_un	s_un;
1613ef9cbf7Sgilles 	int			fd;
1623ef9cbf7Sgilles 	mode_t			old_umask;
1633ef9cbf7Sgilles 
1643ef9cbf7Sgilles 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
1653ef9cbf7Sgilles 		fatal("control: socket");
1663ef9cbf7Sgilles 
167ac61da4aSgilles 	memset(&s_un, 0, sizeof(s_un));
168ac61da4aSgilles 	s_un.sun_family = AF_UNIX;
169ac61da4aSgilles 	if (strlcpy(s_un.sun_path, SMTPD_SOCKET,
170ac61da4aSgilles 	    sizeof(s_un.sun_path)) >= sizeof(s_un.sun_path))
1713ef9cbf7Sgilles 		fatal("control: socket name too long");
1723ef9cbf7Sgilles 
173ac61da4aSgilles 	if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == 0)
1749ede5fd3Sjacekm 		fatalx("control socket already listening");
1759ede5fd3Sjacekm 
1763ef9cbf7Sgilles 	if (unlink(SMTPD_SOCKET) == -1)
1773ef9cbf7Sgilles 		if (errno != ENOENT)
1783ef9cbf7Sgilles 			fatal("control: cannot unlink socket");
1793ef9cbf7Sgilles 
1803ef9cbf7Sgilles 	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
181ac61da4aSgilles 	if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) {
1823ef9cbf7Sgilles 		(void)umask(old_umask);
1833ef9cbf7Sgilles 		fatal("control: bind");
1843ef9cbf7Sgilles 	}
1853ef9cbf7Sgilles 	(void)umask(old_umask);
1863ef9cbf7Sgilles 
1877bdbba2fSeric 	if (chmod(SMTPD_SOCKET,
1887bdbba2fSeric 		S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) {
1893ef9cbf7Sgilles 		(void)unlink(SMTPD_SOCKET);
1903ef9cbf7Sgilles 		fatal("control: chmod");
1913ef9cbf7Sgilles 	}
1923ef9cbf7Sgilles 
193907c4b99Skrw 	io_set_nonblocking(fd);
1943ef9cbf7Sgilles 	control_state.fd = fd;
1953ef9cbf7Sgilles 
1965894db6eSeric 	return fd;
1975894db6eSeric }
1985894db6eSeric 
199b88ab68dSeric int
2005894db6eSeric control(void)
2015894db6eSeric {
2025894db6eSeric 	struct passwd		*pw;
2035894db6eSeric 
2045894db6eSeric 	purge_config(PURGE_EVERYTHING);
2055894db6eSeric 
2065894db6eSeric 	if ((pw = getpwnam(SMTPD_USER)) == NULL)
2075894db6eSeric 		fatalx("unknown user " SMTPD_USER);
2085894db6eSeric 
2099ed3223cSgilles 	stat_backend = env->sc_stat;
2109ed3223cSgilles 	stat_backend->init();
2119ed3223cSgilles 
21211d04e02Seric 	if (chroot(PATH_CHROOT) == -1)
2133ef9cbf7Sgilles 		fatal("control: chroot");
2143ef9cbf7Sgilles 	if (chdir("/") == -1)
2153ef9cbf7Sgilles 		fatal("control: chdir(\"/\")");
2163ef9cbf7Sgilles 
21765c4fdfbSgilles 	config_process(PROC_CONTROL);
2183ef9cbf7Sgilles 
2193ef9cbf7Sgilles 	if (setgroups(1, &pw->pw_gid) ||
2203ef9cbf7Sgilles 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
2213ef9cbf7Sgilles 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
2223ef9cbf7Sgilles 		fatal("control: cannot drop privileges");
2233ef9cbf7Sgilles 
224ed1929b6Sjacekm 	imsg_callback = control_imsg;
2253ef9cbf7Sgilles 	event_init();
2263ef9cbf7Sgilles 
22743962b9cSeric 	signal(SIGINT, SIG_IGN);
22843962b9cSeric 	signal(SIGTERM, SIG_IGN);
2293ef9cbf7Sgilles 	signal(SIGPIPE, SIG_IGN);
2303ef9cbf7Sgilles 	signal(SIGHUP, SIG_IGN);
2313ef9cbf7Sgilles 
23265c4fdfbSgilles 	tree_init(&ctl_conns);
2338e9397b5Sgilles 	tree_init(&ctl_count);
2343ef9cbf7Sgilles 
235c1392a69Seric 	memset(&digest, 0, sizeof digest);
23682614934Seric 	digest.startup = time(NULL);
23782614934Seric 
23865c4fdfbSgilles 	config_peer(PROC_SCHEDULER);
23965c4fdfbSgilles 	config_peer(PROC_QUEUE);
24065c4fdfbSgilles 	config_peer(PROC_PARENT);
24165c4fdfbSgilles 	config_peer(PROC_LKA);
2421a5b831aSmartijn 	config_peer(PROC_DISPATCHER);
24357d312f7Seric 	config_peer(PROC_CA);
24465c4fdfbSgilles 
245e4d36f12Seric 	control_listen();
246e5b07014Sgilles 
247b0d27b30Sgilles 	if (pledge("stdio unix recvfd sendfd", NULL) == -1)
248ff01b044Seric 		fatal("pledge");
249b0d27b30Sgilles 
250f94528c3Seric 	event_dispatch();
251f94528c3Seric 	fatalx("exited event loop");
2523ef9cbf7Sgilles 
2533ef9cbf7Sgilles 	return (0);
2543ef9cbf7Sgilles }
2553ef9cbf7Sgilles 
256104d47d3Seric static void
2573ef9cbf7Sgilles control_shutdown(void)
2583ef9cbf7Sgilles {
25943962b9cSeric 	log_debug("debug: control agent exiting");
2603ef9cbf7Sgilles 	_exit(0);
2613ef9cbf7Sgilles }
2623ef9cbf7Sgilles 
263104d47d3Seric static void
264e4d36f12Seric control_listen(void)
2653ef9cbf7Sgilles {
266cb5c228eSjacekm 	if (listen(control_state.fd, CONTROL_BACKLOG) == -1)
267cb5c228eSjacekm 		fatal("control_listen");
2683ef9cbf7Sgilles 
2693ef9cbf7Sgilles 	event_set(&control_state.ev, control_state.fd, EV_READ|EV_PERSIST,
270e4d36f12Seric 	    control_accept, NULL);
2713ef9cbf7Sgilles 	event_add(&control_state.ev, NULL);
2723ef9cbf7Sgilles }
2733ef9cbf7Sgilles 
274104d47d3Seric static void
2753ef9cbf7Sgilles control_accept(int listenfd, short event, void *arg)
2763ef9cbf7Sgilles {
2773ef9cbf7Sgilles 	int			 connfd;
2783ef9cbf7Sgilles 	socklen_t		 len;
279ac61da4aSgilles 	struct sockaddr_un	 s_un;
2803ef9cbf7Sgilles 	struct ctl_conn		*c;
2818e9397b5Sgilles 	size_t			*count;
2828e9397b5Sgilles 	uid_t			 euid;
2838e9397b5Sgilles 	gid_t			 egid;
2843ef9cbf7Sgilles 
28551474229Sgilles 	if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE)
28651474229Sgilles 		goto pause;
28751474229Sgilles 
288ac61da4aSgilles 	len = sizeof(s_un);
289ac61da4aSgilles 	if ((connfd = accept(listenfd, (struct sockaddr *)&s_un, &len)) == -1) {
29051474229Sgilles 		if (errno == ENFILE || errno == EMFILE)
29151474229Sgilles 			goto pause;
29262e3c252Sderaadt 		if (errno == EINTR || errno == EWOULDBLOCK ||
29362e3c252Sderaadt 		    errno == ECONNABORTED)
2943ef9cbf7Sgilles 			return;
295cb5c228eSjacekm 		fatal("control_accept: accept");
2963ef9cbf7Sgilles 	}
2973ef9cbf7Sgilles 
298907c4b99Skrw 	io_set_nonblocking(connfd);
2993ef9cbf7Sgilles 
3008e9397b5Sgilles 	if (getpeereid(connfd, &euid, &egid) == -1)
30165c4fdfbSgilles 		fatal("getpeereid");
3028e9397b5Sgilles 
3038e9397b5Sgilles 	count = tree_get(&ctl_count, euid);
3048e9397b5Sgilles 	if (count == NULL) {
305118c16f3Sgilles 		count = xcalloc(1, sizeof *count);
3068e9397b5Sgilles 		tree_xset(&ctl_count, euid, count);
3078e9397b5Sgilles 	}
3088e9397b5Sgilles 
3098e9397b5Sgilles 	if (*count == CONTROL_MAXCONN_PER_CLIENT) {
3108e9397b5Sgilles 		close(connfd);
3118e9397b5Sgilles 		log_warnx("warn: too many connections to control socket "
3128e9397b5Sgilles 		    "from user with uid %lu", (unsigned long int)euid);
3138e9397b5Sgilles 		return;
3148e9397b5Sgilles 	}
3158e9397b5Sgilles 	(*count)++;
3168e9397b5Sgilles 
317bf1fa702Sgilles 	do {
318bf1fa702Sgilles 		++connid;
319bf1fa702Sgilles 	} while (tree_get(&ctl_conns, connid));
320bf1fa702Sgilles 
321118c16f3Sgilles 	c = xcalloc(1, sizeof(*c));
3228e9397b5Sgilles 	c->euid = euid;
3238e9397b5Sgilles 	c->egid = egid;
324bf1fa702Sgilles 	c->id = connid;
325299c4efeSeric 	c->mproc.proc = PROC_CLIENT;
32665c4fdfbSgilles 	c->mproc.handler = control_dispatch_ext;
32765c4fdfbSgilles 	c->mproc.data = c;
328fb531d31Santon 	if ((c->mproc.name = strdup(proc_title(c->mproc.proc))) == NULL)
329fb531d31Santon 		fatal("strdup");
33065c4fdfbSgilles 	mproc_init(&c->mproc, connfd);
33165c4fdfbSgilles 	mproc_enable(&c->mproc);
33265c4fdfbSgilles 	tree_xset(&ctl_conns, c->id, c);
333cb5c228eSjacekm 
334e4632cf4Sgilles 	stat_backend->increment("control.session", 1);
33551474229Sgilles 	return;
3369ed3223cSgilles 
33751474229Sgilles pause:
33882614934Seric 	log_warnx("warn: ctl client limit hit, disabling new connections");
339cb5c228eSjacekm 	event_del(&control_state.ev);
340cb5c228eSjacekm }
3413ef9cbf7Sgilles 
342104d47d3Seric static void
343104d47d3Seric control_close(struct ctl_conn *c)
3443ef9cbf7Sgilles {
3458e9397b5Sgilles 	size_t	*count;
3468e9397b5Sgilles 
3478e9397b5Sgilles 	count = tree_xget(&ctl_count, c->euid);
3488e9397b5Sgilles 	(*count)--;
3498e9397b5Sgilles 	if (*count == 0) {
3508e9397b5Sgilles 		tree_xpop(&ctl_count, c->euid);
3518e9397b5Sgilles 		free(count);
3528e9397b5Sgilles 	}
35365c4fdfbSgilles 	tree_xpop(&ctl_conns, c->id);
35465c4fdfbSgilles 	mproc_clear(&c->mproc);
3553ef9cbf7Sgilles 	free(c);
356cb5c228eSjacekm 
357e4632cf4Sgilles 	stat_backend->decrement("control.session", 1);
3589ed3223cSgilles 
35951474229Sgilles 	if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE)
36051474229Sgilles 		return;
36151474229Sgilles 
36251474229Sgilles 	if (!event_pending(&control_state.ev, EV_READ, NULL)) {
36382614934Seric 		log_warnx("warn: re-enabling ctl connections");
364cb5c228eSjacekm 		event_add(&control_state.ev, NULL);
365cb5c228eSjacekm 	}
3663ef9cbf7Sgilles }
3673ef9cbf7Sgilles 
36882614934Seric static void
36982614934Seric control_digest_update(const char *key, size_t value, int incr)
37082614934Seric {
37182614934Seric 	size_t	*p;
37282614934Seric 
37382614934Seric 	p = NULL;
37482614934Seric 
37582614934Seric 	if (!strcmp(key, "smtp.session")) {
37682614934Seric 		if (incr)
37782614934Seric 			p = &digest.clt_connect;
37882614934Seric 		else
37982614934Seric 			digest.clt_disconnect += value;
38082614934Seric 	}
38182614934Seric 	else if (!strcmp(key, "scheduler.envelope")) {
38282614934Seric 		if (incr)
38382614934Seric 			p = &digest.evp_enqueued;
38482614934Seric 		else
38582614934Seric 			digest.evp_dequeued += value;
38682614934Seric 	}
38782614934Seric 	else if  (!strcmp(key, "scheduler.envelope.expired"))
38882614934Seric 		p = &digest.evp_expired;
38982614934Seric 	else if  (!strcmp(key, "scheduler.envelope.removed"))
39082614934Seric 		p = &digest.evp_removed;
39182614934Seric 	else if  (!strcmp(key, "scheduler.delivery.ok"))
39282614934Seric 		p = &digest.dlv_ok;
39382614934Seric 	else if  (!strcmp(key, "scheduler.delivery.permfail"))
39482614934Seric 		p = &digest.dlv_permfail;
39582614934Seric 	else if  (!strcmp(key, "scheduler.delivery.tempfail"))
39682614934Seric 		p = &digest.dlv_tempfail;
39782614934Seric 	else if  (!strcmp(key, "scheduler.delivery.loop"))
39882614934Seric 		p = &digest.dlv_loop;
39982614934Seric 
40082614934Seric 	else if  (!strcmp(key, "queue.bounce"))
40182614934Seric 		p = &digest.evp_bounce;
40282614934Seric 
40382614934Seric 	if (p) {
40482614934Seric 		if (incr)
40582614934Seric 			*p = *p + value;
40682614934Seric 		else
40782614934Seric 			*p = *p - value;
40882614934Seric 	}
40982614934Seric }
41082614934Seric 
411104d47d3Seric static void
41265c4fdfbSgilles control_dispatch_ext(struct mproc *p, struct imsg *imsg)
4133ef9cbf7Sgilles {
4145b6a9ce9Seric 	struct sockaddr_storage	 ss;
4153ef9cbf7Sgilles 	struct ctl_conn		*c;
41665c4fdfbSgilles 	int			 v;
4179ed3223cSgilles 	struct stat_kv		*kvp;
4189ed3223cSgilles 	char			*key;
419e4632cf4Sgilles 	struct stat_value	 val;
420e9283ba6Sgilles 	size_t			 len;
421a9835440Ssunil 	uint64_t		 evpid;
422a9835440Ssunil 	uint32_t		 msgid;
423b7191141Sgilles 
42465c4fdfbSgilles 	c = p->data;
4253ef9cbf7Sgilles 
42665c4fdfbSgilles 	if (imsg == NULL) {
427104d47d3Seric 		control_close(c);
4283ef9cbf7Sgilles 		return;
4293ef9cbf7Sgilles 	}
4303ef9cbf7Sgilles 
431299c4efeSeric 	if (imsg->hdr.peerid != IMSG_VERSION) {
432299c4efeSeric 		m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0);
433299c4efeSeric 		return;
434299c4efeSeric 	}
435299c4efeSeric 
43665c4fdfbSgilles 	switch (imsg->hdr.type) {
437aa1d5973Seric 	case IMSG_CTL_SMTP_SESSION:
438*bf921b2aSclaudio 		imsgbuf_allow_fdpass(&p->imsgbuf);
4394dcd8201Seric 		if (env->sc_flags & SMTPD_SMTP_PAUSED) {
44065c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
44165c4fdfbSgilles 			return;
44221dda8f7Sjacekm 		}
4431a5b831aSmartijn 		m_compose(p_dispatcher, IMSG_CTL_SMTP_SESSION, c->id, 0, -1,
44465c4fdfbSgilles 		    &c->euid, sizeof(c->euid));
44565c4fdfbSgilles 		return;
4469b96bad3Seric 
447aa1d5973Seric 	case IMSG_CTL_GET_DIGEST:
44865c4fdfbSgilles 		if (c->euid)
44982614934Seric 			goto badcred;
45082614934Seric 		digest.timestamp = time(NULL);
451aa1d5973Seric 		m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest);
45265c4fdfbSgilles 		return;
45382614934Seric 
454aa1d5973Seric 	case IMSG_CTL_GET_STATS:
45565c4fdfbSgilles 		if (c->euid)
4569ed3223cSgilles 			goto badcred;
45765c4fdfbSgilles 		kvp = imsg->data;
4589ed3223cSgilles 		if (!stat_backend->iter(&kvp->iter, &key, &val))
4599ed3223cSgilles 			kvp->iter = NULL;
4609ed3223cSgilles 		else {
461e42ff713Sgilles 			(void)strlcpy(kvp->key, key, sizeof kvp->key);
4629ed3223cSgilles 			kvp->val = val;
4639ed3223cSgilles 		}
464aa1d5973Seric 		m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp);
46565c4fdfbSgilles 		return;
4669b96bad3Seric 
4679b96bad3Seric 	case IMSG_CTL_VERBOSE:
46865c4fdfbSgilles 		if (c->euid)
469ba0c8f48Schl 			goto badcred;
470ba0c8f48Schl 
471f24248b7Sreyk 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
472ba0c8f48Schl 			goto badcred;
473ba0c8f48Schl 
47465c4fdfbSgilles 		memcpy(&v, imsg->data, sizeof(v));
475f24248b7Sreyk 		log_trace_verbose(v);
4769b96bad3Seric 
477f24248b7Sreyk 		control_broadcast_verbose(IMSG_CTL_VERBOSE, v);
47865c4fdfbSgilles 
47965c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
48065c4fdfbSgilles 		return;
48165c4fdfbSgilles 
482aa1d5973Seric 	case IMSG_CTL_TRACE_ENABLE:
48365c4fdfbSgilles 		if (c->euid)
48465c4fdfbSgilles 			goto badcred;
48565c4fdfbSgilles 
486f24248b7Sreyk 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
48765c4fdfbSgilles 			goto badcred;
48865c4fdfbSgilles 
48965c4fdfbSgilles 		memcpy(&v, imsg->data, sizeof(v));
490f24248b7Sreyk 		tracing |= v;
491f24248b7Sreyk 		log_trace_verbose(tracing);
49265c4fdfbSgilles 
493f24248b7Sreyk 		control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing);
49465c4fdfbSgilles 
49565c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
49665c4fdfbSgilles 		return;
49765c4fdfbSgilles 
498aa1d5973Seric 	case IMSG_CTL_TRACE_DISABLE:
49965c4fdfbSgilles 		if (c->euid)
50065c4fdfbSgilles 			goto badcred;
50165c4fdfbSgilles 
502f24248b7Sreyk 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
50365c4fdfbSgilles 			goto badcred;
50465c4fdfbSgilles 
50565c4fdfbSgilles 		memcpy(&v, imsg->data, sizeof(v));
506f24248b7Sreyk 		tracing &= ~v;
507f24248b7Sreyk 		log_trace_verbose(tracing);
50865c4fdfbSgilles 
509f24248b7Sreyk 		control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing);
51065c4fdfbSgilles 
51165c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
51265c4fdfbSgilles 		return;
51365c4fdfbSgilles 
514aa1d5973Seric 	case IMSG_CTL_PROFILE_ENABLE:
51565c4fdfbSgilles 		if (c->euid)
51665c4fdfbSgilles 			goto badcred;
51765c4fdfbSgilles 
518f24248b7Sreyk 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
51965c4fdfbSgilles 			goto badcred;
52065c4fdfbSgilles 
52165c4fdfbSgilles 		memcpy(&v, imsg->data, sizeof(v));
52265c4fdfbSgilles 		profiling |= v;
52365c4fdfbSgilles 
52457d312f7Seric 		control_broadcast_verbose(IMSG_CTL_PROFILE, profiling);
52565c4fdfbSgilles 
52665c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
52765c4fdfbSgilles 		return;
52865c4fdfbSgilles 
529aa1d5973Seric 	case IMSG_CTL_PROFILE_DISABLE:
53065c4fdfbSgilles 		if (c->euid)
53165c4fdfbSgilles 			goto badcred;
53265c4fdfbSgilles 
533f24248b7Sreyk 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
53465c4fdfbSgilles 			goto badcred;
53565c4fdfbSgilles 
53665c4fdfbSgilles 		memcpy(&v, imsg->data, sizeof(v));
53765c4fdfbSgilles 		profiling &= ~v;
53865c4fdfbSgilles 
53957d312f7Seric 		control_broadcast_verbose(IMSG_CTL_PROFILE, profiling);
54065c4fdfbSgilles 
54165c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
54265c4fdfbSgilles 		return;
54365c4fdfbSgilles 
54435e161d3Seric 	case IMSG_CTL_PAUSE_EVP:
54535e161d3Seric 		if (c->euid)
54635e161d3Seric 			goto badcred;
54735e161d3Seric 
5480fcb81a3Seric 		imsg->hdr.peerid = c->id;
54935e161d3Seric 		m_forward(p_scheduler, imsg);
55035e161d3Seric 		return;
55135e161d3Seric 
55265c4fdfbSgilles 	case IMSG_CTL_PAUSE_MDA:
55365c4fdfbSgilles 		if (c->euid)
554b7191141Sgilles 			goto badcred;
555b7191141Sgilles 
556df079d11Sgilles 		if (env->sc_flags & SMTPD_MDA_PAUSED) {
55765c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
55865c4fdfbSgilles 			return;
559df079d11Sgilles 		}
56082614934Seric 		log_info("info: mda paused");
561df079d11Sgilles 		env->sc_flags |= SMTPD_MDA_PAUSED;
56265c4fdfbSgilles 		m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0);
56365c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
56465c4fdfbSgilles 		return;
5659b96bad3Seric 
56665c4fdfbSgilles 	case IMSG_CTL_PAUSE_MTA:
56765c4fdfbSgilles 		if (c->euid)
568b7191141Sgilles 			goto badcred;
569b7191141Sgilles 
570df079d11Sgilles 		if (env->sc_flags & SMTPD_MTA_PAUSED) {
57165c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
57265c4fdfbSgilles 			return;
573df079d11Sgilles 		}
57482614934Seric 		log_info("info: mta paused");
575df079d11Sgilles 		env->sc_flags |= SMTPD_MTA_PAUSED;
57665c4fdfbSgilles 		m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0);
57765c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
57865c4fdfbSgilles 		return;
5799b96bad3Seric 
58065c4fdfbSgilles 	case IMSG_CTL_PAUSE_SMTP:
58165c4fdfbSgilles 		if (c->euid)
582b7191141Sgilles 			goto badcred;
583b7191141Sgilles 
584c78ed3ecSgilles 		if (env->sc_flags & SMTPD_SMTP_PAUSED) {
58565c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
58665c4fdfbSgilles 			return;
587c78ed3ecSgilles 		}
58882614934Seric 		log_info("info: smtp paused");
589c78ed3ecSgilles 		env->sc_flags |= SMTPD_SMTP_PAUSED;
5901a5b831aSmartijn 		m_compose(p_dispatcher, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0);
59165c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
59265c4fdfbSgilles 		return;
5939b96bad3Seric 
59435e161d3Seric 	case IMSG_CTL_RESUME_EVP:
59535e161d3Seric 		if (c->euid)
59635e161d3Seric 			goto badcred;
59735e161d3Seric 
5980fcb81a3Seric 		imsg->hdr.peerid = c->id;
59935e161d3Seric 		m_forward(p_scheduler, imsg);
60035e161d3Seric 		return;
60135e161d3Seric 
60265c4fdfbSgilles 	case IMSG_CTL_RESUME_MDA:
60365c4fdfbSgilles 		if (c->euid)
604b7191141Sgilles 			goto badcred;
605b7191141Sgilles 
606df079d11Sgilles 		if (!(env->sc_flags & SMTPD_MDA_PAUSED)) {
60765c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
60865c4fdfbSgilles 			return;
609df079d11Sgilles 		}
61082614934Seric 		log_info("info: mda resumed");
611df079d11Sgilles 		env->sc_flags &= ~SMTPD_MDA_PAUSED;
61265c4fdfbSgilles 		m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0);
61365c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
61465c4fdfbSgilles 		return;
6159b96bad3Seric 
61665c4fdfbSgilles 	case IMSG_CTL_RESUME_MTA:
61765c4fdfbSgilles 		if (c->euid)
618b7191141Sgilles 			goto badcred;
619b7191141Sgilles 
620df079d11Sgilles 		if (!(env->sc_flags & SMTPD_MTA_PAUSED)) {
62165c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
62265c4fdfbSgilles 			return;
623df079d11Sgilles 		}
62482614934Seric 		log_info("info: mta resumed");
625df079d11Sgilles 		env->sc_flags &= ~SMTPD_MTA_PAUSED;
62665c4fdfbSgilles 		m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0);
62765c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
62865c4fdfbSgilles 		return;
629ed5c65a8Sgilles 
63065c4fdfbSgilles 	case IMSG_CTL_RESUME_SMTP:
63165c4fdfbSgilles 		if (c->euid)
632b7191141Sgilles 			goto badcred;
633b7191141Sgilles 
634c78ed3ecSgilles 		if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) {
63565c4fdfbSgilles 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
63665c4fdfbSgilles 			return;
637c78ed3ecSgilles 		}
63882614934Seric 		log_info("info: smtp resumed");
639c78ed3ecSgilles 		env->sc_flags &= ~SMTPD_SMTP_PAUSED;
6401a5b831aSmartijn 		m_forward(p_dispatcher, imsg);
64165c4fdfbSgilles 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
64265c4fdfbSgilles 		return;
643ed5c65a8Sgilles 
644c5acbec8Seric 	case IMSG_CTL_RESUME_ROUTE:
645c5acbec8Seric 		if (c->euid)
646c5acbec8Seric 			goto badcred;
647c5acbec8Seric 
6481a5b831aSmartijn 		m_forward(p_dispatcher, imsg);
649c5acbec8Seric 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
650c5acbec8Seric 		return;
651c5acbec8Seric 
65265c4fdfbSgilles 	case IMSG_CTL_LIST_MESSAGES:
65365c4fdfbSgilles 		if (c->euid)
6544fe02f32Seric 			goto badcred;
65565c4fdfbSgilles 		m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1,
65665c4fdfbSgilles 		    imsg->data, imsg->hdr.len - sizeof(imsg->hdr));
65765c4fdfbSgilles 		return;
6584fe02f32Seric 
65965c4fdfbSgilles 	case IMSG_CTL_LIST_ENVELOPES:
66065c4fdfbSgilles 		if (c->euid)
6614fe02f32Seric 			goto badcred;
66265c4fdfbSgilles 		m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1,
66365c4fdfbSgilles 		    imsg->data, imsg->hdr.len - sizeof(imsg->hdr));
66465c4fdfbSgilles 		return;
6654fe02f32Seric 
666f9a337f4Seric 	case IMSG_CTL_MTA_SHOW_HOSTS:
667f9a337f4Seric 	case IMSG_CTL_MTA_SHOW_RELAYS:
668c5acbec8Seric 	case IMSG_CTL_MTA_SHOW_ROUTES:
669c5acbec8Seric 	case IMSG_CTL_MTA_SHOW_HOSTSTATS:
6705b6a9ce9Seric 	case IMSG_CTL_MTA_SHOW_BLOCK:
671c5acbec8Seric 		if (c->euid)
672c5acbec8Seric 			goto badcred;
6730fcb81a3Seric 
6740fcb81a3Seric 		imsg->hdr.peerid = c->id;
6751a5b831aSmartijn 		m_forward(p_dispatcher, imsg);
676c5acbec8Seric 		return;
677c5acbec8Seric 
678c37e9483Seric 	case IMSG_CTL_SHOW_STATUS:
679c37e9483Seric 		if (c->euid)
680c37e9483Seric 			goto badcred;
681c37e9483Seric 
682c37e9483Seric 		m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags,
683c37e9483Seric 		    sizeof(env->sc_flags));
684c37e9483Seric 		return;
685c37e9483Seric 
6865b6a9ce9Seric 	case IMSG_CTL_MTA_BLOCK:
6875b6a9ce9Seric 	case IMSG_CTL_MTA_UNBLOCK:
6885b6a9ce9Seric 		if (c->euid)
6895b6a9ce9Seric 			goto badcred;
6905b6a9ce9Seric 
6915b6a9ce9Seric 		if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss))
6925b6a9ce9Seric 			goto invalid;
6935b6a9ce9Seric 		memmove(&ss, imsg->data, sizeof(ss));
6941a5b831aSmartijn 		m_create(p_dispatcher, imsg->hdr.type, c->id, 0, -1);
6951a5b831aSmartijn 		m_add_sockaddr(p_dispatcher, (struct sockaddr *)&ss);
6961a5b831aSmartijn 		m_add_string(p_dispatcher, (char *)imsg->data + sizeof(ss));
6971a5b831aSmartijn 		m_close(p_dispatcher);
6985b6a9ce9Seric 		return;
6995b6a9ce9Seric 
70065c4fdfbSgilles 	case IMSG_CTL_SCHEDULE:
70165c4fdfbSgilles 		if (c->euid)
702ed5c65a8Sgilles 			goto badcred;
703ed5c65a8Sgilles 
7040fcb81a3Seric 		imsg->hdr.peerid = c->id;
70565c4fdfbSgilles 		m_forward(p_scheduler, imsg);
70665c4fdfbSgilles 		return;
707ed5c65a8Sgilles 
70865c4fdfbSgilles 	case IMSG_CTL_REMOVE:
70965c4fdfbSgilles 		if (c->euid)
710ed5c65a8Sgilles 			goto badcred;
711ed5c65a8Sgilles 
7120fcb81a3Seric 		imsg->hdr.peerid = c->id;
71365c4fdfbSgilles 		m_forward(p_scheduler, imsg);
71465c4fdfbSgilles 		return;
7159b96bad3Seric 
716aa1d5973Seric 	case IMSG_CTL_UPDATE_TABLE:
71765c4fdfbSgilles 		if (c->euid)
718e9283ba6Sgilles 			goto badcred;
719e9283ba6Sgilles 
72065c4fdfbSgilles 		/* table name too long */
72165c4fdfbSgilles 		len = strlen(imsg->data);
722953aae25Sderaadt 		if (len >= LINE_MAX)
723e9283ba6Sgilles 			goto invalid;
724e9283ba6Sgilles 
725bbe9c651Ssunil 		imsg->hdr.peerid = c->id;
72665c4fdfbSgilles 		m_forward(p_lka, imsg);
72765c4fdfbSgilles 		return;
728e9283ba6Sgilles 
729a9835440Ssunil 	case IMSG_CTL_DISCOVER_EVPID:
730a9835440Ssunil 		if (c->euid)
731a9835440Ssunil 			goto badcred;
732a9835440Ssunil 
733a9835440Ssunil 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid)
734a9835440Ssunil 			goto invalid;
735a9835440Ssunil 
736a9835440Ssunil 		memmove(&evpid, imsg->data, sizeof evpid);
737a9835440Ssunil 		m_create(p_queue, imsg->hdr.type, c->id, 0, -1);
738a9835440Ssunil 		m_add_evpid(p_queue, evpid);
739a9835440Ssunil 		m_close(p_queue);
740a9835440Ssunil 		return;
741a9835440Ssunil 
742a9835440Ssunil 	case IMSG_CTL_DISCOVER_MSGID:
743a9835440Ssunil 		if (c->euid)
744a9835440Ssunil 			goto badcred;
745a9835440Ssunil 
746a9835440Ssunil 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid)
747a9835440Ssunil 			goto invalid;
748a9835440Ssunil 
749a9835440Ssunil 		memmove(&msgid, imsg->data, sizeof msgid);
750a9835440Ssunil 		m_create(p_queue, imsg->hdr.type, c->id, 0, -1);
751a9835440Ssunil 		m_add_msgid(p_queue, msgid);
752a9835440Ssunil 		m_close(p_queue);
753a9835440Ssunil 		return;
754a9835440Ssunil 
7553ef9cbf7Sgilles 	default:
75682614934Seric 		log_debug("debug: control_dispatch_ext: "
7576d095224Schl 		    "error handling %s imsg",
75865c4fdfbSgilles 		    imsg_to_str(imsg->hdr.type));
75965c4fdfbSgilles 		return;
7603ef9cbf7Sgilles 	}
761b7191141Sgilles badcred:
762e9283ba6Sgilles invalid:
76365c4fdfbSgilles 	m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
7643ef9cbf7Sgilles }
76557d312f7Seric 
76657d312f7Seric static void
76757d312f7Seric control_broadcast_verbose(int msg, int v)
76857d312f7Seric {
76957d312f7Seric 	m_create(p_lka, msg, 0, 0, -1);
77057d312f7Seric 	m_add_int(p_lka, v);
77157d312f7Seric 	m_close(p_lka);
77257d312f7Seric 
7731a5b831aSmartijn 	m_create(p_dispatcher, msg, 0, 0, -1);
7741a5b831aSmartijn 	m_add_int(p_dispatcher, v);
7751a5b831aSmartijn 	m_close(p_dispatcher);
77657d312f7Seric 
77757d312f7Seric 	m_create(p_queue, msg, 0, 0, -1);
77857d312f7Seric 	m_add_int(p_queue, v);
77957d312f7Seric 	m_close(p_queue);
78057d312f7Seric 
78157d312f7Seric 	m_create(p_ca, msg, 0, 0, -1);
78257d312f7Seric 	m_add_int(p_ca, v);
78357d312f7Seric 	m_close(p_ca);
78457d312f7Seric 
78557d312f7Seric 	m_create(p_scheduler, msg, 0, 0, -1);
78657d312f7Seric 	m_add_int(p_scheduler, v);
78757d312f7Seric 	m_close(p_scheduler);
78857d312f7Seric 
78957d312f7Seric 	m_create(p_parent, msg, 0, 0, -1);
79057d312f7Seric 	m_add_int(p_parent, v);
79157d312f7Seric 	m_close(p_parent);
79257d312f7Seric }
793