1*6676295fSclaudio /* $OpenBSD: control.c,v 1.64 2024/11/21 13:38:45 claudio Exp $ */ 2feb9ff76Sreyk 3feb9ff76Sreyk /* 4feb9ff76Sreyk * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> 5feb9ff76Sreyk * 6feb9ff76Sreyk * Permission to use, copy, modify, and distribute this software for any 7feb9ff76Sreyk * purpose with or without fee is hereby granted, provided that the above 8feb9ff76Sreyk * copyright notice and this permission notice appear in all copies. 9feb9ff76Sreyk * 10feb9ff76Sreyk * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11feb9ff76Sreyk * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12feb9ff76Sreyk * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13feb9ff76Sreyk * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14feb9ff76Sreyk * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15feb9ff76Sreyk * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16feb9ff76Sreyk * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17feb9ff76Sreyk */ 18feb9ff76Sreyk 19feb9ff76Sreyk #include <sys/queue.h> 20feb9ff76Sreyk #include <sys/stat.h> 21feb9ff76Sreyk #include <sys/socket.h> 22f04ff968Sreyk #include <sys/time.h> 23feb9ff76Sreyk #include <sys/un.h> 240ca734d7Sreyk 25feb9ff76Sreyk #include <errno.h> 26feb9ff76Sreyk #include <event.h> 27feb9ff76Sreyk #include <fcntl.h> 28feb9ff76Sreyk #include <stdlib.h> 29feb9ff76Sreyk #include <string.h> 30feb9ff76Sreyk #include <unistd.h> 31f04ff968Sreyk #include <imsg.h> 32e8fb3979Spyr 33748ceb64Sreyk #include "relayd.h" 34feb9ff76Sreyk 35feb9ff76Sreyk #define CONTROL_BACKLOG 5 36feb9ff76Sreyk 37c5fa57f5Sdv struct ctl_connlist ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); 38feb9ff76Sreyk 390325c666Sreyk void control_accept(int, short, void *); 406b61fee8Sderaadt void control_close(int, struct control_sock *); 41feb9ff76Sreyk 42feb9ff76Sreyk int 430325c666Sreyk control_init(struct privsep *ps, struct control_sock *cs) 44feb9ff76Sreyk { 450325c666Sreyk struct relayd *env = ps->ps_env; 46feb9ff76Sreyk struct sockaddr_un sun; 47feb9ff76Sreyk int fd; 480325c666Sreyk mode_t old_umask, mode; 490325c666Sreyk 500325c666Sreyk if (cs->cs_name == NULL) 510325c666Sreyk return (0); 52feb9ff76Sreyk 53b045ffeeSreyk if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) { 5485a8c65fSreyk log_warn("%s: socket", __func__); 55feb9ff76Sreyk return (-1); 56feb9ff76Sreyk } 57feb9ff76Sreyk 58feb9ff76Sreyk sun.sun_family = AF_UNIX; 590325c666Sreyk if (strlcpy(sun.sun_path, cs->cs_name, 60a82f8bfbSreyk sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { 610325c666Sreyk log_warn("%s: %s name too long", __func__, cs->cs_name); 62a82f8bfbSreyk close(fd); 63a82f8bfbSreyk return (-1); 64a82f8bfbSreyk } 65feb9ff76Sreyk 660325c666Sreyk if (unlink(cs->cs_name) == -1) 67feb9ff76Sreyk if (errno != ENOENT) { 680325c666Sreyk log_warn("%s: unlink %s", __func__, cs->cs_name); 69feb9ff76Sreyk close(fd); 70feb9ff76Sreyk return (-1); 71feb9ff76Sreyk } 72feb9ff76Sreyk 730325c666Sreyk if (cs->cs_restricted) { 740325c666Sreyk old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); 750325c666Sreyk mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; 760325c666Sreyk } else { 77feb9ff76Sreyk old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); 780325c666Sreyk mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; 790325c666Sreyk } 800325c666Sreyk 81feb9ff76Sreyk if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { 820325c666Sreyk log_warn("%s: bind: %s", __func__, cs->cs_name); 83feb9ff76Sreyk close(fd); 84a82f8bfbSreyk (void)umask(old_umask); 85feb9ff76Sreyk return (-1); 86feb9ff76Sreyk } 87a82f8bfbSreyk (void)umask(old_umask); 88feb9ff76Sreyk 890325c666Sreyk if (chmod(cs->cs_name, mode) == -1) { 9085a8c65fSreyk log_warn("%s: chmod", __func__); 91feb9ff76Sreyk close(fd); 920325c666Sreyk (void)unlink(cs->cs_name); 93feb9ff76Sreyk return (-1); 94feb9ff76Sreyk } 95feb9ff76Sreyk 960325c666Sreyk cs->cs_fd = fd; 970325c666Sreyk cs->cs_env = env; 98feb9ff76Sreyk 99feb9ff76Sreyk return (0); 100feb9ff76Sreyk } 101feb9ff76Sreyk 102feb9ff76Sreyk int 1030325c666Sreyk control_listen(struct control_sock *cs) 104feb9ff76Sreyk { 1050325c666Sreyk if (cs->cs_name == NULL) 1060325c666Sreyk return (0); 107feb9ff76Sreyk 1080325c666Sreyk if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) { 10985a8c65fSreyk log_warn("%s: listen", __func__); 110feb9ff76Sreyk return (-1); 111feb9ff76Sreyk } 112feb9ff76Sreyk 1136b61fee8Sderaadt event_set(&cs->cs_ev, cs->cs_fd, EV_READ, 1146b61fee8Sderaadt control_accept, cs); 1150325c666Sreyk event_add(&cs->cs_ev, NULL); 1166b61fee8Sderaadt evtimer_set(&cs->cs_evt, control_accept, cs); 117feb9ff76Sreyk 118feb9ff76Sreyk return (0); 119feb9ff76Sreyk } 120feb9ff76Sreyk 121feb9ff76Sreyk void 1220325c666Sreyk control_cleanup(struct control_sock *cs) 123feb9ff76Sreyk { 1240325c666Sreyk if (cs->cs_name == NULL) 1250325c666Sreyk return; 1266b61fee8Sderaadt event_del(&cs->cs_ev); 1276b61fee8Sderaadt event_del(&cs->cs_evt); 128feb9ff76Sreyk } 129feb9ff76Sreyk 130feb9ff76Sreyk void 131feb9ff76Sreyk control_accept(int listenfd, short event, void *arg) 132feb9ff76Sreyk { 133feb9ff76Sreyk int connfd; 134feb9ff76Sreyk socklen_t len; 135feb9ff76Sreyk struct sockaddr_un sun; 136feb9ff76Sreyk struct ctl_conn *c; 1376b61fee8Sderaadt struct control_sock *cs = arg; 1386b61fee8Sderaadt 1396b61fee8Sderaadt event_add(&cs->cs_ev, NULL); 1406b61fee8Sderaadt if ((event & EV_TIMEOUT)) 1416b61fee8Sderaadt return; 142feb9ff76Sreyk 143feb9ff76Sreyk len = sizeof(sun); 144b045ffeeSreyk if ((connfd = accept4(listenfd, 145b045ffeeSreyk (struct sockaddr *)&sun, &len, SOCK_NONBLOCK)) == -1) { 1466b61fee8Sderaadt /* 1476b61fee8Sderaadt * Pause accept if we are out of file descriptors, or 1486b61fee8Sderaadt * libevent will haunt us here too. 1496b61fee8Sderaadt */ 1506b61fee8Sderaadt if (errno == ENFILE || errno == EMFILE) { 1516b61fee8Sderaadt struct timeval evtpause = { 1, 0 }; 1526b61fee8Sderaadt 1536b61fee8Sderaadt event_del(&cs->cs_ev); 1546b61fee8Sderaadt evtimer_add(&cs->cs_evt, &evtpause); 15562e3c252Sderaadt } else if (errno != EWOULDBLOCK && errno != EINTR && 15662e3c252Sderaadt errno != ECONNABORTED) 15785a8c65fSreyk log_warn("%s: accept", __func__); 158feb9ff76Sreyk return; 159feb9ff76Sreyk } 160feb9ff76Sreyk 161e8494736Sreyk if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { 16285a8c65fSreyk log_warn("%s: calloc", __func__); 163*6676295fSclaudio close(connfd); 164feb9ff76Sreyk return; 165feb9ff76Sreyk } 166feb9ff76Sreyk 167*6676295fSclaudio if (imsgbuf_init(&c->iev.ibuf, connfd) == -1) { 168*6676295fSclaudio log_warn("%s: imsgbuf_init", __func__); 169*6676295fSclaudio close(connfd); 170*6676295fSclaudio free(c); 171*6676295fSclaudio return; 172*6676295fSclaudio } 173*6676295fSclaudio 174f07d0e3bSpyr c->iev.handler = control_dispatch_imsg; 175f07d0e3bSpyr c->iev.events = EV_READ; 1763cc2d6a3Sderaadt c->iev.data = cs; /* proc.c cheats (reuses the handler) */ 177f07d0e3bSpyr event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, 1786b61fee8Sderaadt c->iev.handler, cs); 179f07d0e3bSpyr event_add(&c->iev.ev, NULL); 180feb9ff76Sreyk 181feb9ff76Sreyk TAILQ_INSERT_TAIL(&ctl_conns, c, entry); 182feb9ff76Sreyk } 183feb9ff76Sreyk 184feb9ff76Sreyk struct ctl_conn * 185feb9ff76Sreyk control_connbyfd(int fd) 186feb9ff76Sreyk { 187feb9ff76Sreyk struct ctl_conn *c; 188feb9ff76Sreyk 1894ff7cad5Skrw TAILQ_FOREACH(c, &ctl_conns, entry) { 1904ff7cad5Skrw if (c->iev.ibuf.fd == fd) 1914ff7cad5Skrw break; 1924ff7cad5Skrw } 193feb9ff76Sreyk 194feb9ff76Sreyk return (c); 195feb9ff76Sreyk } 196feb9ff76Sreyk 197feb9ff76Sreyk void 1986b61fee8Sderaadt control_close(int fd, struct control_sock *cs) 199feb9ff76Sreyk { 200feb9ff76Sreyk struct ctl_conn *c; 201feb9ff76Sreyk 202bca0e58eSclaudio if ((c = control_connbyfd(fd)) == NULL) { 20385a8c65fSreyk log_warn("%s: fd %d not found", __func__, fd); 204bca0e58eSclaudio return; 205bca0e58eSclaudio } 206feb9ff76Sreyk 2079cbf9e90Sclaudio imsgbuf_clear(&c->iev.ibuf); 208feb9ff76Sreyk TAILQ_REMOVE(&ctl_conns, c, entry); 209feb9ff76Sreyk 210f07d0e3bSpyr event_del(&c->iev.ev); 211f07d0e3bSpyr close(c->iev.ibuf.fd); 2126b61fee8Sderaadt 2136b61fee8Sderaadt /* Some file descriptors are available again. */ 2146b61fee8Sderaadt if (evtimer_pending(&cs->cs_evt, NULL)) { 2156b61fee8Sderaadt evtimer_del(&cs->cs_evt); 2166b61fee8Sderaadt event_add(&cs->cs_ev, NULL); 2176b61fee8Sderaadt } 2186b61fee8Sderaadt 219feb9ff76Sreyk free(c); 220feb9ff76Sreyk } 221feb9ff76Sreyk 222feb9ff76Sreyk void 223feb9ff76Sreyk control_dispatch_imsg(int fd, short event, void *arg) 224feb9ff76Sreyk { 2253cc2d6a3Sderaadt struct control_sock *cs = arg; 226feb9ff76Sreyk struct ctl_conn *c; 227feb9ff76Sreyk struct imsg imsg; 228ef1f2334Sreyk struct ctl_id id; 229feb9ff76Sreyk int n; 230f579a0f7Sjsg int verbose; 2316b61fee8Sderaadt struct relayd *env = cs->cs_env; 232c28c61ccSreyk struct privsep *ps = env->sc_ps; 233feb9ff76Sreyk 234feb9ff76Sreyk if ((c = control_connbyfd(fd)) == NULL) { 23585a8c65fSreyk log_warn("%s: fd %d not found", __func__, fd); 236feb9ff76Sreyk return; 237feb9ff76Sreyk } 238feb9ff76Sreyk 2392668107aSreyk if (event & EV_READ) { 240668e5ba9Sclaudio if (imsgbuf_read(&c->iev.ibuf) != 1) { 2416b61fee8Sderaadt control_close(fd, cs); 242feb9ff76Sreyk return; 243feb9ff76Sreyk } 2442668107aSreyk } 2452668107aSreyk 2462668107aSreyk if (event & EV_WRITE) { 247dd7efffeSclaudio if (imsgbuf_write(&c->iev.ibuf) == -1) { 2486b61fee8Sderaadt control_close(fd, cs); 249feb9ff76Sreyk return; 250feb9ff76Sreyk } 251feb9ff76Sreyk } 252feb9ff76Sreyk 253feb9ff76Sreyk for (;;) { 254f07d0e3bSpyr if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { 2556b61fee8Sderaadt control_close(fd, cs); 256feb9ff76Sreyk return; 257feb9ff76Sreyk } 258feb9ff76Sreyk 259feb9ff76Sreyk if (n == 0) 260feb9ff76Sreyk break; 261feb9ff76Sreyk 26286b74329Sreyk if (c->waiting) { 26386b74329Sreyk log_debug("%s: unexpected imsg %d", 26486b74329Sreyk __func__, imsg.hdr.type); 26586b74329Sreyk imsg_free(&imsg); 2666b61fee8Sderaadt control_close(fd, cs); 26786b74329Sreyk return; 26886b74329Sreyk } 26986b74329Sreyk 270feb9ff76Sreyk switch (imsg.hdr.type) { 271feb9ff76Sreyk case IMSG_CTL_SHOW_SUM: 272feb9ff76Sreyk show(c); 273feb9ff76Sreyk break; 2749d421a7aSreyk case IMSG_CTL_SESSION: 2759d421a7aSreyk show_sessions(c); 2769d421a7aSreyk break; 2779591a9f7Spyr case IMSG_CTL_RDR_DISABLE: 278feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 279feb9ff76Sreyk fatalx("invalid imsg header len"); 280feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 2819591a9f7Spyr if (disable_rdr(c, &id)) 282f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 283c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 2841569a65fSpyr else { 2851569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 286c28c61ccSreyk control_imsg_forward(ps, &imsg); 287f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 288c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 2891569a65fSpyr } 290feb9ff76Sreyk break; 2919591a9f7Spyr case IMSG_CTL_RDR_ENABLE: 292feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 293feb9ff76Sreyk fatalx("invalid imsg header len"); 294feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 2959591a9f7Spyr if (enable_rdr(c, &id)) 296f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 297c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 2981569a65fSpyr else { 2991569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 300c28c61ccSreyk control_imsg_forward(ps, &imsg); 301f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 302c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3031569a65fSpyr } 304feb9ff76Sreyk break; 305feb9ff76Sreyk case IMSG_CTL_TABLE_DISABLE: 306feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 307feb9ff76Sreyk fatalx("invalid imsg header len"); 308feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 309ef1f2334Sreyk if (disable_table(c, &id)) 310f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 311c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3121569a65fSpyr else { 3131569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 314c28c61ccSreyk control_imsg_forward(ps, &imsg); 315f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 316c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3171569a65fSpyr } 318feb9ff76Sreyk break; 319feb9ff76Sreyk case IMSG_CTL_TABLE_ENABLE: 320feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 321feb9ff76Sreyk fatalx("invalid imsg header len"); 322feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 323ef1f2334Sreyk if (enable_table(c, &id)) 324f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 325c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3261569a65fSpyr else { 3271569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 328c28c61ccSreyk control_imsg_forward(ps, &imsg); 329f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 330c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3311569a65fSpyr } 332feb9ff76Sreyk break; 333feb9ff76Sreyk case IMSG_CTL_HOST_DISABLE: 334feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 335feb9ff76Sreyk fatalx("invalid imsg header len"); 336feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 337c723f8edSreyk if (disable_host(c, &id, NULL)) 338f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 339c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3401569a65fSpyr else { 3411569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 342c28c61ccSreyk control_imsg_forward(ps, &imsg); 343f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 344c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3451569a65fSpyr } 346feb9ff76Sreyk break; 347feb9ff76Sreyk case IMSG_CTL_HOST_ENABLE: 348feb9ff76Sreyk if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(id)) 349feb9ff76Sreyk fatalx("invalid imsg header len"); 350feb9ff76Sreyk memcpy(&id, imsg.data, sizeof(id)); 351c723f8edSreyk if (enable_host(c, &id, NULL)) 352f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 353c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3541569a65fSpyr else { 3551569a65fSpyr memcpy(imsg.data, &id, sizeof(id)); 356c28c61ccSreyk control_imsg_forward(ps, &imsg); 357f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 358c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3591569a65fSpyr } 360feb9ff76Sreyk break; 361feb9ff76Sreyk case IMSG_CTL_SHUTDOWN: 362a2195becSreyk case IMSG_CTL_RELOAD: 363a2195becSreyk proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 364feb9ff76Sreyk break; 365cd65ce7bSpyr case IMSG_CTL_POLL: 3662166201eSreyk proc_compose(env->sc_ps, PROC_HCE, 3672166201eSreyk IMSG_CTL_POLL, NULL, 0); 368f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_OK, 369c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 370cd65ce7bSpyr break; 3711569a65fSpyr case IMSG_CTL_NOTIFY: 3721569a65fSpyr if (c->flags & CTL_CONN_NOTIFY) { 37385a8c65fSreyk log_debug("%s: " 37485a8c65fSreyk "client requested notify more than once", 37585a8c65fSreyk __func__); 376f07d0e3bSpyr imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 377c28c61ccSreyk 0, ps->ps_instance + 1, -1, NULL, 0); 3781569a65fSpyr break; 3791569a65fSpyr } 3801569a65fSpyr c->flags |= CTL_CONN_NOTIFY; 3811569a65fSpyr break; 3820325c666Sreyk case IMSG_CTL_VERBOSE: 3830325c666Sreyk IMSG_SIZE_CHECK(&imsg, &verbose); 384f579a0f7Sjsg 385f579a0f7Sjsg memcpy(&verbose, imsg.data, sizeof(verbose)); 386f579a0f7Sjsg 387a2195becSreyk proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 388a2195becSreyk proc_forward_imsg(env->sc_ps, &imsg, PROC_HCE, -1); 3890325c666Sreyk proc_forward_imsg(env->sc_ps, &imsg, PROC_RELAY, -1); 3900325c666Sreyk 391f579a0f7Sjsg memcpy(imsg.data, &verbose, sizeof(verbose)); 392c28c61ccSreyk control_imsg_forward(ps, &imsg); 393871fc12cSreyk log_setverbose(verbose); 394f579a0f7Sjsg break; 395feb9ff76Sreyk default: 39685a8c65fSreyk log_debug("%s: error handling imsg %d", 39785a8c65fSreyk __func__, imsg.hdr.type); 398feb9ff76Sreyk break; 399feb9ff76Sreyk } 400feb9ff76Sreyk imsg_free(&imsg); 401feb9ff76Sreyk } 402feb9ff76Sreyk 403f07d0e3bSpyr imsg_event_add(&c->iev); 404feb9ff76Sreyk } 405feb9ff76Sreyk 406feb9ff76Sreyk void 407c28c61ccSreyk control_imsg_forward(struct privsep *ps, struct imsg *imsg) 4081569a65fSpyr { 4091569a65fSpyr struct ctl_conn *c; 4101569a65fSpyr 4111569a65fSpyr TAILQ_FOREACH(c, &ctl_conns, entry) 4121569a65fSpyr if (c->flags & CTL_CONN_NOTIFY) 413f07d0e3bSpyr imsg_compose_event(&c->iev, imsg->hdr.type, 414c28c61ccSreyk 0, ps->ps_instance + 1, -1, imsg->data, 415069bf5e4Spyr imsg->hdr.len - IMSG_HEADER_SIZE); 4161569a65fSpyr } 417