xref: /openbsd-src/usr.sbin/smtpd/control.c (revision fc405d53b73a2d73393cb97f684863d17b583e38)
1 /*	$OpenBSD: control.c,v 1.129 2023/03/08 04:43:15 guenther Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
5  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
6  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/stat.h>
22 #include <sys/un.h>
23 
24 #include <errno.h>
25 #include <pwd.h>
26 #include <signal.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "smtpd.h"
32 #include "log.h"
33 
34 #define CONTROL_BACKLOG 5
35 
36 struct ctl_conn {
37 	uint32_t		 id;
38 	uint8_t			 flags;
39 #define CTL_CONN_NOTIFY		 0x01
40 	struct mproc		 mproc;
41 	uid_t			 euid;
42 	gid_t			 egid;
43 };
44 
45 struct {
46 	struct event		 ev;
47 	int			 fd;
48 } control_state;
49 
50 static void control_imsg(struct mproc *, struct imsg *);
51 static void control_shutdown(void);
52 static void control_listen(void);
53 static void control_accept(int, short, void *);
54 static void control_close(struct ctl_conn *);
55 static void control_dispatch_ext(struct mproc *, struct imsg *);
56 static void control_digest_update(const char *, size_t, int);
57 static void control_broadcast_verbose(int, int);
58 
59 static struct stat_backend *stat_backend = NULL;
60 extern const char *backend_stat;
61 
62 static uint64_t			connid = 0;
63 static struct tree		ctl_conns;
64 static struct tree		ctl_count;
65 static struct stat_digest	digest;
66 
67 #define	CONTROL_FD_RESERVE		5
68 #define	CONTROL_MAXCONN_PER_CLIENT	32
69 
70 static void
71 control_imsg(struct mproc *p, struct imsg *imsg)
72 {
73 	struct ctl_conn		*c;
74 	struct stat_value	 val;
75 	struct msg		 m;
76 	const char		*key;
77 	const void		*data;
78 	size_t			 sz;
79 
80 	if (imsg == NULL) {
81 		if (p->proc != PROC_CLIENT)
82 			control_shutdown();
83 		return;
84 	}
85 
86 	switch (imsg->hdr.type) {
87 	case IMSG_CTL_OK:
88 	case IMSG_CTL_FAIL:
89 	case IMSG_CTL_LIST_MESSAGES:
90 	case IMSG_CTL_LIST_ENVELOPES:
91 	case IMSG_CTL_DISCOVER_EVPID:
92 	case IMSG_CTL_DISCOVER_MSGID:
93 	case IMSG_CTL_MTA_SHOW_HOSTS:
94 	case IMSG_CTL_MTA_SHOW_RELAYS:
95 	case IMSG_CTL_MTA_SHOW_ROUTES:
96 	case IMSG_CTL_MTA_SHOW_HOSTSTATS:
97 	case IMSG_CTL_MTA_SHOW_BLOCK:
98 		c = tree_get(&ctl_conns, imsg->hdr.peerid);
99 		if (c == NULL)
100 			return;
101 		imsg->hdr.peerid = 0;
102 		m_forward(&c->mproc, imsg);
103 		return;
104 
105 	case IMSG_CTL_SMTP_SESSION:
106 		c = tree_get(&ctl_conns, imsg->hdr.peerid);
107 		if (c == NULL)
108 			return;
109 		m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg->fd, NULL, 0);
110 		return;
111 
112 	case IMSG_STAT_INCREMENT:
113 		m_msg(&m, imsg);
114 		m_get_string(&m, &key);
115 		m_get_data(&m, &data, &sz);
116 		m_end(&m);
117 		if (sz != sizeof(val))
118 			fatalx("control: IMSG_STAT_INCREMENT size mismatch");
119 		memmove(&val, data, sz);
120 		if (stat_backend)
121 			stat_backend->increment(key, val.u.counter);
122 		control_digest_update(key, val.u.counter, 1);
123 		return;
124 
125 	case IMSG_STAT_DECREMENT:
126 		m_msg(&m, imsg);
127 		m_get_string(&m, &key);
128 		m_get_data(&m, &data, &sz);
129 		m_end(&m);
130 		if (sz != sizeof(val))
131 			fatalx("control: IMSG_STAT_DECREMENT size mismatch");
132 		memmove(&val, data, sz);
133 		if (stat_backend)
134 			stat_backend->decrement(key, val.u.counter);
135 		control_digest_update(key, val.u.counter, 0);
136 		return;
137 
138 	case IMSG_STAT_SET:
139 		m_msg(&m, imsg);
140 		m_get_string(&m, &key);
141 		m_get_data(&m, &data, &sz);
142 		m_end(&m);
143 		if (sz != sizeof(val))
144 			fatalx("control: IMSG_STAT_SET size mismatch");
145 		memmove(&val, data, sz);
146 		if (stat_backend)
147 			stat_backend->set(key, &val);
148 		return;
149 	}
150 
151 	fatalx("control_imsg: unexpected %s imsg",
152 	    imsg_to_str(imsg->hdr.type));
153 }
154 
155 int
156 control_create_socket(void)
157 {
158 	struct sockaddr_un	s_un;
159 	int			fd;
160 	mode_t			old_umask;
161 
162 	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
163 		fatal("control: socket");
164 
165 	memset(&s_un, 0, sizeof(s_un));
166 	s_un.sun_family = AF_UNIX;
167 	if (strlcpy(s_un.sun_path, SMTPD_SOCKET,
168 	    sizeof(s_un.sun_path)) >= sizeof(s_un.sun_path))
169 		fatal("control: socket name too long");
170 
171 	if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == 0)
172 		fatalx("control socket already listening");
173 
174 	if (unlink(SMTPD_SOCKET) == -1)
175 		if (errno != ENOENT)
176 			fatal("control: cannot unlink socket");
177 
178 	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
179 	if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) {
180 		(void)umask(old_umask);
181 		fatal("control: bind");
182 	}
183 	(void)umask(old_umask);
184 
185 	if (chmod(SMTPD_SOCKET,
186 		S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) {
187 		(void)unlink(SMTPD_SOCKET);
188 		fatal("control: chmod");
189 	}
190 
191 	io_set_nonblocking(fd);
192 	control_state.fd = fd;
193 
194 	return fd;
195 }
196 
197 int
198 control(void)
199 {
200 	struct passwd		*pw;
201 
202 	purge_config(PURGE_EVERYTHING);
203 
204 	if ((pw = getpwnam(SMTPD_USER)) == NULL)
205 		fatalx("unknown user " SMTPD_USER);
206 
207 	stat_backend = env->sc_stat;
208 	stat_backend->init();
209 
210 	if (chroot(PATH_CHROOT) == -1)
211 		fatal("control: chroot");
212 	if (chdir("/") == -1)
213 		fatal("control: chdir(\"/\")");
214 
215 	config_process(PROC_CONTROL);
216 
217 	if (setgroups(1, &pw->pw_gid) ||
218 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
219 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
220 		fatal("control: cannot drop privileges");
221 
222 	imsg_callback = control_imsg;
223 	event_init();
224 
225 	signal(SIGINT, SIG_IGN);
226 	signal(SIGTERM, SIG_IGN);
227 	signal(SIGPIPE, SIG_IGN);
228 	signal(SIGHUP, SIG_IGN);
229 
230 	tree_init(&ctl_conns);
231 	tree_init(&ctl_count);
232 
233 	memset(&digest, 0, sizeof digest);
234 	digest.startup = time(NULL);
235 
236 	config_peer(PROC_SCHEDULER);
237 	config_peer(PROC_QUEUE);
238 	config_peer(PROC_PARENT);
239 	config_peer(PROC_LKA);
240 	config_peer(PROC_DISPATCHER);
241 	config_peer(PROC_CA);
242 
243 	control_listen();
244 
245 	if (pledge("stdio unix recvfd sendfd", NULL) == -1)
246 		fatal("pledge");
247 
248 	event_dispatch();
249 	fatalx("exited event loop");
250 
251 	return (0);
252 }
253 
254 static void
255 control_shutdown(void)
256 {
257 	log_debug("debug: control agent exiting");
258 	_exit(0);
259 }
260 
261 static void
262 control_listen(void)
263 {
264 	if (listen(control_state.fd, CONTROL_BACKLOG) == -1)
265 		fatal("control_listen");
266 
267 	event_set(&control_state.ev, control_state.fd, EV_READ|EV_PERSIST,
268 	    control_accept, NULL);
269 	event_add(&control_state.ev, NULL);
270 }
271 
272 static void
273 control_accept(int listenfd, short event, void *arg)
274 {
275 	int			 connfd;
276 	socklen_t		 len;
277 	struct sockaddr_un	 s_un;
278 	struct ctl_conn		*c;
279 	size_t			*count;
280 	uid_t			 euid;
281 	gid_t			 egid;
282 
283 	if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE)
284 		goto pause;
285 
286 	len = sizeof(s_un);
287 	if ((connfd = accept(listenfd, (struct sockaddr *)&s_un, &len)) == -1) {
288 		if (errno == ENFILE || errno == EMFILE)
289 			goto pause;
290 		if (errno == EINTR || errno == EWOULDBLOCK ||
291 		    errno == ECONNABORTED)
292 			return;
293 		fatal("control_accept: accept");
294 	}
295 
296 	io_set_nonblocking(connfd);
297 
298 	if (getpeereid(connfd, &euid, &egid) == -1)
299 		fatal("getpeereid");
300 
301 	count = tree_get(&ctl_count, euid);
302 	if (count == NULL) {
303 		count = xcalloc(1, sizeof *count);
304 		tree_xset(&ctl_count, euid, count);
305 	}
306 
307 	if (*count == CONTROL_MAXCONN_PER_CLIENT) {
308 		close(connfd);
309 		log_warnx("warn: too many connections to control socket "
310 		    "from user with uid %lu", (unsigned long int)euid);
311 		return;
312 	}
313 	(*count)++;
314 
315 	do {
316 		++connid;
317 	} while (tree_get(&ctl_conns, connid));
318 
319 	c = xcalloc(1, sizeof(*c));
320 	c->euid = euid;
321 	c->egid = egid;
322 	c->id = connid;
323 	c->mproc.proc = PROC_CLIENT;
324 	c->mproc.handler = control_dispatch_ext;
325 	c->mproc.data = c;
326 	if ((c->mproc.name = strdup(proc_title(c->mproc.proc))) == NULL)
327 		fatal("strdup");
328 	mproc_init(&c->mproc, connfd);
329 	mproc_enable(&c->mproc);
330 	tree_xset(&ctl_conns, c->id, c);
331 
332 	stat_backend->increment("control.session", 1);
333 	return;
334 
335 pause:
336 	log_warnx("warn: ctl client limit hit, disabling new connections");
337 	event_del(&control_state.ev);
338 }
339 
340 static void
341 control_close(struct ctl_conn *c)
342 {
343 	size_t	*count;
344 
345 	count = tree_xget(&ctl_count, c->euid);
346 	(*count)--;
347 	if (*count == 0) {
348 		tree_xpop(&ctl_count, c->euid);
349 		free(count);
350 	}
351 	tree_xpop(&ctl_conns, c->id);
352 	mproc_clear(&c->mproc);
353 	free(c);
354 
355 	stat_backend->decrement("control.session", 1);
356 
357 	if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE)
358 		return;
359 
360 	if (!event_pending(&control_state.ev, EV_READ, NULL)) {
361 		log_warnx("warn: re-enabling ctl connections");
362 		event_add(&control_state.ev, NULL);
363 	}
364 }
365 
366 static void
367 control_digest_update(const char *key, size_t value, int incr)
368 {
369 	size_t	*p;
370 
371 	p = NULL;
372 
373 	if (!strcmp(key, "smtp.session")) {
374 		if (incr)
375 			p = &digest.clt_connect;
376 		else
377 			digest.clt_disconnect += value;
378 	}
379 	else if (!strcmp(key, "scheduler.envelope")) {
380 		if (incr)
381 			p = &digest.evp_enqueued;
382 		else
383 			digest.evp_dequeued += value;
384 	}
385 	else if  (!strcmp(key, "scheduler.envelope.expired"))
386 		p = &digest.evp_expired;
387 	else if  (!strcmp(key, "scheduler.envelope.removed"))
388 		p = &digest.evp_removed;
389 	else if  (!strcmp(key, "scheduler.delivery.ok"))
390 		p = &digest.dlv_ok;
391 	else if  (!strcmp(key, "scheduler.delivery.permfail"))
392 		p = &digest.dlv_permfail;
393 	else if  (!strcmp(key, "scheduler.delivery.tempfail"))
394 		p = &digest.dlv_tempfail;
395 	else if  (!strcmp(key, "scheduler.delivery.loop"))
396 		p = &digest.dlv_loop;
397 
398 	else if  (!strcmp(key, "queue.bounce"))
399 		p = &digest.evp_bounce;
400 
401 	if (p) {
402 		if (incr)
403 			*p = *p + value;
404 		else
405 			*p = *p - value;
406 	}
407 }
408 
409 static void
410 control_dispatch_ext(struct mproc *p, struct imsg *imsg)
411 {
412 	struct sockaddr_storage	 ss;
413 	struct ctl_conn		*c;
414 	int			 v;
415 	struct stat_kv		*kvp;
416 	char			*key;
417 	struct stat_value	 val;
418 	size_t			 len;
419 	uint64_t		 evpid;
420 	uint32_t		 msgid;
421 
422 	c = p->data;
423 
424 	if (imsg == NULL) {
425 		control_close(c);
426 		return;
427 	}
428 
429 	if (imsg->hdr.peerid != IMSG_VERSION) {
430 		m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0);
431 		return;
432 	}
433 
434 	switch (imsg->hdr.type) {
435 	case IMSG_CTL_SMTP_SESSION:
436 		if (env->sc_flags & SMTPD_SMTP_PAUSED) {
437 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
438 			return;
439 		}
440 		m_compose(p_dispatcher, IMSG_CTL_SMTP_SESSION, c->id, 0, -1,
441 		    &c->euid, sizeof(c->euid));
442 		return;
443 
444 	case IMSG_CTL_GET_DIGEST:
445 		if (c->euid)
446 			goto badcred;
447 		digest.timestamp = time(NULL);
448 		m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest);
449 		return;
450 
451 	case IMSG_CTL_GET_STATS:
452 		if (c->euid)
453 			goto badcred;
454 		kvp = imsg->data;
455 		if (!stat_backend->iter(&kvp->iter, &key, &val))
456 			kvp->iter = NULL;
457 		else {
458 			(void)strlcpy(kvp->key, key, sizeof kvp->key);
459 			kvp->val = val;
460 		}
461 		m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp);
462 		return;
463 
464 	case IMSG_CTL_VERBOSE:
465 		if (c->euid)
466 			goto badcred;
467 
468 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
469 			goto badcred;
470 
471 		memcpy(&v, imsg->data, sizeof(v));
472 		log_trace_verbose(v);
473 
474 		control_broadcast_verbose(IMSG_CTL_VERBOSE, v);
475 
476 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
477 		return;
478 
479 	case IMSG_CTL_TRACE_ENABLE:
480 		if (c->euid)
481 			goto badcred;
482 
483 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
484 			goto badcred;
485 
486 		memcpy(&v, imsg->data, sizeof(v));
487 		tracing |= v;
488 		log_trace_verbose(tracing);
489 
490 		control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing);
491 
492 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
493 		return;
494 
495 	case IMSG_CTL_TRACE_DISABLE:
496 		if (c->euid)
497 			goto badcred;
498 
499 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
500 			goto badcred;
501 
502 		memcpy(&v, imsg->data, sizeof(v));
503 		tracing &= ~v;
504 		log_trace_verbose(tracing);
505 
506 		control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing);
507 
508 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
509 		return;
510 
511 	case IMSG_CTL_PROFILE_ENABLE:
512 		if (c->euid)
513 			goto badcred;
514 
515 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
516 			goto badcred;
517 
518 		memcpy(&v, imsg->data, sizeof(v));
519 		profiling |= v;
520 
521 		control_broadcast_verbose(IMSG_CTL_PROFILE, profiling);
522 
523 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
524 		return;
525 
526 	case IMSG_CTL_PROFILE_DISABLE:
527 		if (c->euid)
528 			goto badcred;
529 
530 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v))
531 			goto badcred;
532 
533 		memcpy(&v, imsg->data, sizeof(v));
534 		profiling &= ~v;
535 
536 		control_broadcast_verbose(IMSG_CTL_PROFILE, profiling);
537 
538 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
539 		return;
540 
541 	case IMSG_CTL_PAUSE_EVP:
542 		if (c->euid)
543 			goto badcred;
544 
545 		imsg->hdr.peerid = c->id;
546 		m_forward(p_scheduler, imsg);
547 		return;
548 
549 	case IMSG_CTL_PAUSE_MDA:
550 		if (c->euid)
551 			goto badcred;
552 
553 		if (env->sc_flags & SMTPD_MDA_PAUSED) {
554 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
555 			return;
556 		}
557 		log_info("info: mda paused");
558 		env->sc_flags |= SMTPD_MDA_PAUSED;
559 		m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0);
560 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
561 		return;
562 
563 	case IMSG_CTL_PAUSE_MTA:
564 		if (c->euid)
565 			goto badcred;
566 
567 		if (env->sc_flags & SMTPD_MTA_PAUSED) {
568 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
569 			return;
570 		}
571 		log_info("info: mta paused");
572 		env->sc_flags |= SMTPD_MTA_PAUSED;
573 		m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0);
574 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
575 		return;
576 
577 	case IMSG_CTL_PAUSE_SMTP:
578 		if (c->euid)
579 			goto badcred;
580 
581 		if (env->sc_flags & SMTPD_SMTP_PAUSED) {
582 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
583 			return;
584 		}
585 		log_info("info: smtp paused");
586 		env->sc_flags |= SMTPD_SMTP_PAUSED;
587 		m_compose(p_dispatcher, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0);
588 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
589 		return;
590 
591 	case IMSG_CTL_RESUME_EVP:
592 		if (c->euid)
593 			goto badcred;
594 
595 		imsg->hdr.peerid = c->id;
596 		m_forward(p_scheduler, imsg);
597 		return;
598 
599 	case IMSG_CTL_RESUME_MDA:
600 		if (c->euid)
601 			goto badcred;
602 
603 		if (!(env->sc_flags & SMTPD_MDA_PAUSED)) {
604 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
605 			return;
606 		}
607 		log_info("info: mda resumed");
608 		env->sc_flags &= ~SMTPD_MDA_PAUSED;
609 		m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0);
610 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
611 		return;
612 
613 	case IMSG_CTL_RESUME_MTA:
614 		if (c->euid)
615 			goto badcred;
616 
617 		if (!(env->sc_flags & SMTPD_MTA_PAUSED)) {
618 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
619 			return;
620 		}
621 		log_info("info: mta resumed");
622 		env->sc_flags &= ~SMTPD_MTA_PAUSED;
623 		m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0);
624 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
625 		return;
626 
627 	case IMSG_CTL_RESUME_SMTP:
628 		if (c->euid)
629 			goto badcred;
630 
631 		if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) {
632 			m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
633 			return;
634 		}
635 		log_info("info: smtp resumed");
636 		env->sc_flags &= ~SMTPD_SMTP_PAUSED;
637 		m_forward(p_dispatcher, imsg);
638 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
639 		return;
640 
641 	case IMSG_CTL_RESUME_ROUTE:
642 		if (c->euid)
643 			goto badcred;
644 
645 		m_forward(p_dispatcher, imsg);
646 		m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0);
647 		return;
648 
649 	case IMSG_CTL_LIST_MESSAGES:
650 		if (c->euid)
651 			goto badcred;
652 		m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1,
653 		    imsg->data, imsg->hdr.len - sizeof(imsg->hdr));
654 		return;
655 
656 	case IMSG_CTL_LIST_ENVELOPES:
657 		if (c->euid)
658 			goto badcred;
659 		m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1,
660 		    imsg->data, imsg->hdr.len - sizeof(imsg->hdr));
661 		return;
662 
663 	case IMSG_CTL_MTA_SHOW_HOSTS:
664 	case IMSG_CTL_MTA_SHOW_RELAYS:
665 	case IMSG_CTL_MTA_SHOW_ROUTES:
666 	case IMSG_CTL_MTA_SHOW_HOSTSTATS:
667 	case IMSG_CTL_MTA_SHOW_BLOCK:
668 		if (c->euid)
669 			goto badcred;
670 
671 		imsg->hdr.peerid = c->id;
672 		m_forward(p_dispatcher, imsg);
673 		return;
674 
675 	case IMSG_CTL_SHOW_STATUS:
676 		if (c->euid)
677 			goto badcred;
678 
679 		m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags,
680 		    sizeof(env->sc_flags));
681 		return;
682 
683 	case IMSG_CTL_MTA_BLOCK:
684 	case IMSG_CTL_MTA_UNBLOCK:
685 		if (c->euid)
686 			goto badcred;
687 
688 		if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss))
689 			goto invalid;
690 		memmove(&ss, imsg->data, sizeof(ss));
691 		m_create(p_dispatcher, imsg->hdr.type, c->id, 0, -1);
692 		m_add_sockaddr(p_dispatcher, (struct sockaddr *)&ss);
693 		m_add_string(p_dispatcher, (char *)imsg->data + sizeof(ss));
694 		m_close(p_dispatcher);
695 		return;
696 
697 	case IMSG_CTL_SCHEDULE:
698 		if (c->euid)
699 			goto badcred;
700 
701 		imsg->hdr.peerid = c->id;
702 		m_forward(p_scheduler, imsg);
703 		return;
704 
705 	case IMSG_CTL_REMOVE:
706 		if (c->euid)
707 			goto badcred;
708 
709 		imsg->hdr.peerid = c->id;
710 		m_forward(p_scheduler, imsg);
711 		return;
712 
713 	case IMSG_CTL_UPDATE_TABLE:
714 		if (c->euid)
715 			goto badcred;
716 
717 		/* table name too long */
718 		len = strlen(imsg->data);
719 		if (len >= LINE_MAX)
720 			goto invalid;
721 
722 		imsg->hdr.peerid = c->id;
723 		m_forward(p_lka, imsg);
724 		return;
725 
726 	case IMSG_CTL_DISCOVER_EVPID:
727 		if (c->euid)
728 			goto badcred;
729 
730 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid)
731 			goto invalid;
732 
733 		memmove(&evpid, imsg->data, sizeof evpid);
734 		m_create(p_queue, imsg->hdr.type, c->id, 0, -1);
735 		m_add_evpid(p_queue, evpid);
736 		m_close(p_queue);
737 		return;
738 
739 	case IMSG_CTL_DISCOVER_MSGID:
740 		if (c->euid)
741 			goto badcred;
742 
743 		if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid)
744 			goto invalid;
745 
746 		memmove(&msgid, imsg->data, sizeof msgid);
747 		m_create(p_queue, imsg->hdr.type, c->id, 0, -1);
748 		m_add_msgid(p_queue, msgid);
749 		m_close(p_queue);
750 		return;
751 
752 	default:
753 		log_debug("debug: control_dispatch_ext: "
754 		    "error handling %s imsg",
755 		    imsg_to_str(imsg->hdr.type));
756 		return;
757 	}
758 badcred:
759 invalid:
760 	m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0);
761 }
762 
763 static void
764 control_broadcast_verbose(int msg, int v)
765 {
766 	m_create(p_lka, msg, 0, 0, -1);
767 	m_add_int(p_lka, v);
768 	m_close(p_lka);
769 
770 	m_create(p_dispatcher, msg, 0, 0, -1);
771 	m_add_int(p_dispatcher, v);
772 	m_close(p_dispatcher);
773 
774 	m_create(p_queue, msg, 0, 0, -1);
775 	m_add_int(p_queue, v);
776 	m_close(p_queue);
777 
778 	m_create(p_ca, msg, 0, 0, -1);
779 	m_add_int(p_ca, v);
780 	m_close(p_ca);
781 
782 	m_create(p_scheduler, msg, 0, 0, -1);
783 	m_add_int(p_scheduler, v);
784 	m_close(p_scheduler);
785 
786 	m_create(p_parent, msg, 0, 0, -1);
787 	m_add_int(p_parent, v);
788 	m_close(p_parent);
789 }
790