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