Home | History | Annotate | Line # | Download | only in src
      1 /*
      2  * dhcpcd - DHCP client daemon
      3  * SPDX-License-Identifier: BSD-2-Clause
      4  * Copyright (c) 2006-2025 Roy Marples <roy (at) marples.name>
      5  * All rights reserved
      6 
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/socket.h>
     30 #include <sys/stat.h>
     31 #include <sys/uio.h>
     32 #include <sys/un.h>
     33 
     34 #include <errno.h>
     35 #include <fcntl.h>
     36 #include <grp.h>
     37 #include <pwd.h>
     38 #include <stdio.h>
     39 #include <stdlib.h>
     40 #include <string.h>
     41 #include <time.h>
     42 #include <unistd.h>
     43 
     44 #include "config.h" // IWYU pragma: keep
     45 #include "common.h"
     46 #include "control.h"
     47 #include "dhcpcd.h"
     48 #include "eloop.h"
     49 #include "if.h"
     50 #include "logerr.h"
     51 #include "privsep.h"
     52 
     53 #ifndef SUN_LEN
     54 #define SUN_LEN(su) \
     55 	(sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
     56 #endif
     57 
     58 #define SUN_MODE_USR   (S_IRUSR | S_IWUSR)
     59 #define SUN_MODE_GRP   (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
     60 #define SUN_MODE_ALL   (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
     61 #define GID_SET(gid)   ((gid) != (gid_t) - 1)
     62 
     63 #define LISTEN_BACKLOG 5
     64 
     65 static void control_handle_data(void *, unsigned short);
     66 
     67 static void
     68 control_queue_free(struct fd_list *fd)
     69 {
     70 	struct fd_data *fdp;
     71 
     72 	while ((fdp = TAILQ_FIRST(&fd->queue))) {
     73 		TAILQ_REMOVE(&fd->queue, fdp, next);
     74 		if (fdp->data_size != 0)
     75 			free(fdp->data);
     76 		free(fdp);
     77 	}
     78 
     79 #ifdef CTL_FREE_LIST
     80 	while ((fdp = TAILQ_FIRST(&fd->free_queue))) {
     81 		TAILQ_REMOVE(&fd->free_queue, fdp, next);
     82 		if (fdp->data_size != 0)
     83 			free(fdp->data);
     84 		free(fdp);
     85 	}
     86 #endif
     87 }
     88 
     89 void
     90 control_free(struct fd_list *fd)
     91 {
     92 	eloop_event_delete(fd->ctx->eloop, fd->fd);
     93 	close(fd->fd);
     94 	TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
     95 	control_queue_free(fd);
     96 	free(fd);
     97 }
     98 
     99 static void
    100 control_hangup(struct fd_list *fd)
    101 {
    102 	control_free(fd);
    103 }
    104 
    105 static ssize_t
    106 control_handle_read(struct fd_list *fd)
    107 {
    108 	uid_t uid;
    109 	gid_t gid, in_gid;
    110 	char buf[BUFSIZ];
    111 	struct iovec iov[] = { {
    112 	    .iov_base = buf,
    113 	    .iov_len = sizeof(buf),
    114 	} };
    115 	struct msghdr msg = {
    116 		.msg_iov = iov,
    117 		.msg_iovlen = __arraycount(iov),
    118 	};
    119 	ssize_t bytes, err;
    120 
    121 	bytes = recvmsg(fd->fd, &msg, 0);
    122 	if (bytes == -1)
    123 		logerr(__func__);
    124 	if (bytes == -1 || bytes == 0)
    125 		return bytes;
    126 
    127 	if (getpeereid(fd->fd, &uid, &gid) == -1) {
    128 		logerr("%s: getpeereid", __func__);
    129 		return -1;
    130 	}
    131 
    132 #ifdef PRIVSEP
    133 	if (IN_PRIVSEP(fd->ctx)) {
    134 		in_gid = fd->ctx->control_group;
    135 		err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid);
    136 		if (err == -1) {
    137 			logerr(__func__);
    138 			return -1;
    139 		}
    140 		if (err == 0) {
    141 			fd->flags &= ~FD_CONTROL;
    142 			in_gid = fd->ctx->read_group;
    143 			err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid);
    144 			if (err == -1) {
    145 				logerr(__func__);
    146 				return -1;
    147 			}
    148 			if (err == 0)
    149 				fd->flags &= ~FD_READ;
    150 			else
    151 				fd->flags |= FD_READ;
    152 		} else
    153 			fd->flags |= FD_CONTROL | FD_READ;
    154 
    155 		err = ps_ctl_handleargs(fd, buf, (size_t)bytes);
    156 		if (err == -1) {
    157 			logerr(__func__);
    158 			return 0;
    159 		}
    160 
    161 		if (fd->flags & FD_LISTEN)
    162 			fd->flags &= ~FD_COMMAND;
    163 		else
    164 			fd->flags |= FD_COMMAND;
    165 
    166 		iov[0].iov_len = (size_t)bytes;
    167 		/* If ps_ctl_handleargs returns 0 that means it didn't
    168 		 * do anything with the command, so pass it to the manager. */
    169 		if (err == 0 && ps_ctl_sendmsg(fd, &msg) == -1) {
    170 			logerr(__func__);
    171 			return -1;
    172 		}
    173 		return 1;
    174 	}
    175 #endif
    176 
    177 	in_gid = fd->ctx->control_group;
    178 	err = control_user_ingroup(uid, gid, in_gid);
    179 	if (err == -1) {
    180 		logerr(__func__);
    181 		return -1;
    182 	}
    183 	if (err == 0) {
    184 		fd->flags &= ~FD_CONTROL;
    185 		in_gid = fd->ctx->read_group;
    186 		err = control_user_ingroup(uid, gid, in_gid);
    187 		if (err == -1) {
    188 			logerr(__func__);
    189 			return -1;
    190 		}
    191 		if (err == 0)
    192 			fd->flags &= ~FD_READ;
    193 		else
    194 			fd->flags |= FD_READ;
    195 	} else
    196 		fd->flags |= FD_CONTROL | FD_READ;
    197 	return control_recvmsg(fd, &msg, (size_t)bytes);
    198 }
    199 
    200 static ssize_t
    201 control_handle_write(struct fd_list *fd)
    202 {
    203 	struct iovec iov[4];
    204 	struct msghdr msg = { .msg_iov = iov };
    205 	struct fd_data *data;
    206 	ssize_t len;
    207 #ifdef PRIVSEP
    208 	size_t peer_len;
    209 #endif
    210 
    211 	data = TAILQ_FIRST(&fd->queue);
    212 
    213 #ifdef PRIVSEP
    214 	if (data->data_peer_id != 0) {
    215 		/* Control messages for a peer are prefixed with it's fd
    216 		 * fd and message length. */
    217 		iov[msg.msg_iovlen].iov_base = &data->data_peer_id;
    218 		iov[msg.msg_iovlen].iov_len = sizeof(data->data_peer_id);
    219 		msg.msg_iovlen++;
    220 
    221 		peer_len = data->data_len;
    222 		if (data->data_flags & FD_DATA_SENDLEN)
    223 			peer_len += sizeof(data->data_len);
    224 		iov[msg.msg_iovlen].iov_base = &peer_len;
    225 		iov[msg.msg_iovlen].iov_len = sizeof(peer_len);
    226 		msg.msg_iovlen++;
    227 	}
    228 #endif
    229 
    230 	if (data->data_flags & FD_DATA_SENDLEN) {
    231 		iov[msg.msg_iovlen].iov_base = &data->data_len;
    232 		iov[msg.msg_iovlen].iov_len = sizeof(data->data_len);
    233 		msg.msg_iovlen++;
    234 	}
    235 
    236 	iov[msg.msg_iovlen].iov_base = data->data;
    237 	iov[msg.msg_iovlen].iov_len = data->data_len;
    238 	msg.msg_iovlen++;
    239 
    240 	len = sendmsg(fd->fd, &msg, 0);
    241 	if (len == -1) {
    242 		if (errno != EPIPE && errno != ENOTCONN) {
    243 			// We don't get ELE_HANGUP for some reason
    244 			logerr("%s: write", __func__);
    245 		}
    246 		control_hangup(fd);
    247 		return -1;
    248 	}
    249 
    250 	TAILQ_REMOVE(&fd->queue, data, next);
    251 #ifdef CTL_FREE_LIST
    252 	TAILQ_INSERT_TAIL(&fd->free_queue, data, next);
    253 #else
    254 	if (data->data_size != 0)
    255 		free(data->data);
    256 	free(data);
    257 #endif
    258 
    259 	if (TAILQ_FIRST(&fd->queue) != NULL)
    260 		return len;
    261 
    262 	/* Done sending data, stop watching write to fd */
    263 	if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ,
    264 		control_handle_data, fd) == -1)
    265 		logerr("%s: eloop_event_add", __func__);
    266 	return len;
    267 }
    268 
    269 static void
    270 control_handle_data(void *arg, unsigned short events)
    271 {
    272 	struct fd_list *fd = arg;
    273 	ssize_t err;
    274 
    275 	if (events & ELE_HANGUP)
    276 		goto hangup;
    277 
    278 	if (!(events & (ELE_READ | ELE_WRITE | ELE_HANGUP)))
    279 		logerrx("%s: unexpected event 0x%04x", __func__, events);
    280 
    281 	if (events & ELE_WRITE && !(events & ELE_HANGUP)) {
    282 		err = control_handle_write(fd);
    283 		if (err == -1)
    284 			goto hangup;
    285 	}
    286 	if (events & ELE_READ) {
    287 		err = control_handle_read(fd);
    288 		if ((err == -1 && errno != EPERM) || err == 0)
    289 			goto hangup;
    290 	}
    291 
    292 	return;
    293 
    294 hangup:
    295 	control_hangup(fd);
    296 }
    297 
    298 int
    299 control_recvmsg(struct fd_list *fd, struct msghdr *msg, size_t len)
    300 {
    301 	struct iovec *iov;
    302 	char *p, *e;
    303 	char *argvp[255], **ap;
    304 	int argc;
    305 
    306 	if (msg->msg_iovlen == 0) {
    307 		errno = EINVAL;
    308 		return -1;
    309 	}
    310 
    311 	iov = msg->msg_iov;
    312 	p = (char *)iov->iov_base;
    313 
    314 	/* Each command is \n terminated
    315 	 * Each argument is NULL separated */
    316 	while (len != 0) {
    317 		argc = 0;
    318 		ap = argvp;
    319 		while (len != 0) {
    320 			if (*p == '\0') {
    321 				p++;
    322 				len--;
    323 				continue;
    324 			}
    325 			e = memchr(p, '\0', len);
    326 			if (e == NULL) {
    327 				errno = EINVAL;
    328 				logerrx("%s: no terminator", __func__);
    329 				return -1;
    330 			}
    331 			if ((size_t)argc + 1 >=
    332 			    sizeof(argvp) / sizeof(argvp[0])) {
    333 				errno = ENOBUFS;
    334 				logerrx("%s: no arg buffer", __func__);
    335 				return -1;
    336 			}
    337 			*ap++ = p;
    338 			argc++;
    339 			e++;
    340 			len -= (size_t)(e - p);
    341 			p = e;
    342 			e--;
    343 			if (*(--e) == '\n') {
    344 				*e = '\0';
    345 				break;
    346 			}
    347 		}
    348 		if (argc == 0) {
    349 			logerrx("%s: no args", __func__);
    350 			continue;
    351 		}
    352 		*ap = NULL;
    353 		if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) {
    354 			logerr("%s: dhcpcd_handleargs", __func__);
    355 			if (errno != EINTR && errno != EAGAIN && errno != EPERM)
    356 				return -1;
    357 		}
    358 		if (!(fd->flags & FD_LISTEN))
    359 			fd->flags |= FD_COMMAND;
    360 	}
    361 
    362 	return 1;
    363 }
    364 
    365 struct fd_list *
    366 control_find(struct dhcpcd_ctx *ctx, int fd)
    367 {
    368 	struct fd_list *l;
    369 
    370 	TAILQ_FOREACH(l, &ctx->control_fds, next) {
    371 		if (l->fd == fd)
    372 			return l;
    373 	}
    374 
    375 	errno = ESRCH;
    376 	return NULL;
    377 }
    378 
    379 struct fd_list *
    380 control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags)
    381 {
    382 	struct fd_list *l;
    383 	size_t cnt;
    384 	struct fd_list *n;
    385 #ifdef PRIVSEP
    386 	unsigned int id;
    387 #endif
    388 
    389 	l = control_find(ctx, fd);
    390 	if (l != NULL) {
    391 		l->flags = flags;
    392 		return l;
    393 	}
    394 
    395 #ifdef PRIVSEP
    396 again:
    397 	id = ++ctx->ps_control_id;
    398 	if (id == 0) /* wrapped */
    399 		id = ++ctx->ps_control_id;
    400 #endif
    401 	cnt = 0;
    402 	TAILQ_FOREACH(n, &ctx->control_fds, next) {
    403 		if (++cnt >= CONTROL_PEER_MAX) {
    404 			errno = ENOBUFS;
    405 			return NULL;
    406 		}
    407 #ifdef PRIVSEP
    408 		if (n->id == id)
    409 			goto again;
    410 #endif
    411 	}
    412 
    413 	l = malloc(sizeof(*l));
    414 	if (l == NULL)
    415 		return NULL;
    416 
    417 	l->ctx = ctx;
    418 	l->fd = fd;
    419 	l->flags = flags;
    420 	TAILQ_INIT(&l->queue);
    421 #ifdef CTL_FREE_LIST
    422 	TAILQ_INIT(&l->free_queue);
    423 #endif
    424 #ifdef PRIVSEP
    425 	l->peer_id = 0;
    426 	l->id = id;
    427 #endif
    428 	TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
    429 	return l;
    430 }
    431 
    432 static void
    433 control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags,
    434     unsigned short events)
    435 {
    436 	struct sockaddr_un run;
    437 	socklen_t len;
    438 	struct fd_list *l;
    439 	int fd, flags = 1;
    440 
    441 	if (events != ELE_READ)
    442 		logerrx("%s: unexpected event 0x%04x", __func__, events);
    443 
    444 	len = sizeof(run);
    445 	if ((fd = accept(lfd, (struct sockaddr *)&run, &len)) == -1)
    446 		goto error;
    447 	if ((flags = fcntl(fd, F_GETFD, 0)) == -1 ||
    448 	    fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
    449 		goto error;
    450 	if ((flags = fcntl(fd, F_GETFL, 0)) == -1 ||
    451 	    fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
    452 		goto error;
    453 
    454 	l = control_new(ctx, fd, fd_flags);
    455 	if (l == NULL)
    456 		goto error;
    457 
    458 	if (eloop_event_add(ctx->eloop, l->fd, ELE_READ, control_handle_data,
    459 		l) == -1)
    460 		logerr("%s: eloop_event_add", __func__);
    461 
    462 	return;
    463 
    464 error:
    465 	logerr(__func__);
    466 	if (fd != -1)
    467 		close(fd);
    468 }
    469 
    470 static void
    471 control_handle(void *arg, unsigned short events)
    472 {
    473 	struct dhcpcd_ctx *ctx = arg;
    474 
    475 	control_handle1(ctx, ctx->control_fd, 0, events);
    476 }
    477 
    478 static int
    479 make_path(char *path, size_t len, const char *ifname, sa_family_t family)
    480 {
    481 	const char *per;
    482 
    483 	switch (family) {
    484 	case AF_INET:
    485 		per = "-4";
    486 		break;
    487 	case AF_INET6:
    488 		per = "-6";
    489 		break;
    490 	default:
    491 		per = "";
    492 		break;
    493 	}
    494 	return snprintf(path, len, CONTROLSOCKET, ifname ? ifname : "",
    495 	    ifname ? per : "", "", ifname ? "." : "");
    496 }
    497 
    498 static int
    499 make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family)
    500 {
    501 	int fd;
    502 
    503 	if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1)
    504 		return -1;
    505 	memset(sa, 0, sizeof(*sa));
    506 	sa->sun_family = AF_UNIX;
    507 	make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family);
    508 	return fd;
    509 }
    510 
    511 static int
    512 control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family)
    513 {
    514 	struct sockaddr_un sa;
    515 	mode_t mode;
    516 	gid_t grp = getgid();
    517 	int fd;
    518 	socklen_t len;
    519 
    520 	fd = make_sock(&sa, ifname, family);
    521 	if (fd == -1)
    522 		return -1;
    523 
    524 	len = (socklen_t)SUN_LEN(&sa);
    525 	if (GID_SET(ctx->control_group) && GID_SET(ctx->read_group)) {
    526 		if (ctx->control_group == ctx->read_group) {
    527 			mode = SUN_MODE_GRP;
    528 			grp = ctx->control_group;
    529 		} else
    530 			mode = SUN_MODE_ALL;
    531 	} else if (!GID_SET(ctx->control_group) && !GID_SET(ctx->read_group))
    532 		mode = SUN_MODE_USR;
    533 	else if (GID_SET(ctx->control_group)) {
    534 		mode = SUN_MODE_GRP;
    535 		grp = ctx->control_group;
    536 	} else {
    537 		mode = SUN_MODE_GRP;
    538 		grp = ctx->read_group;
    539 	}
    540 
    541 	unlink(sa.sun_path);
    542 	if (bind(fd, (struct sockaddr *)&sa, len) == -1 ||
    543 	    chmod(sa.sun_path, mode) == -1 ||
    544 	    chown(sa.sun_path, geteuid(), grp) == -1 ||
    545 	    listen(fd, LISTEN_BACKLOG) == -1)
    546 		goto err;
    547 
    548 #ifdef PRIVSEP_RIGHTS
    549 	if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_getsockopt(fd) == -1)
    550 		goto err;
    551 #endif
    552 
    553 	strlcpy(ctx->control_sock, sa.sun_path, sizeof(ctx->control_sock));
    554 	return fd;
    555 
    556 err:
    557 	close(fd);
    558 	unlink(sa.sun_path);
    559 	return -1;
    560 }
    561 
    562 int
    563 control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family)
    564 {
    565 	int fd;
    566 
    567 #ifdef PRIVSEP
    568 	if (IN_PRIVSEP_SE(ctx)) {
    569 		make_path(ctx->control_sock, sizeof(ctx->control_sock), ifname,
    570 		    family);
    571 		return 0;
    572 	}
    573 #endif
    574 
    575 	if ((fd = control_start1(ctx, ifname, family)) == -1)
    576 		return -1;
    577 
    578 	ctx->control_fd = fd;
    579 	if (eloop_event_add(ctx->eloop, fd, ELE_READ, control_handle, ctx) ==
    580 	    -1)
    581 		logerr("%s: eloop_event_add", __func__);
    582 
    583 	return ctx->control_fd;
    584 }
    585 
    586 static int
    587 control_unlink(struct dhcpcd_ctx *ctx, const char *file)
    588 {
    589 	int retval = 0;
    590 
    591 	errno = 0;
    592 #ifdef PRIVSEP
    593 	if (IN_PRIVSEP(ctx))
    594 		retval = (int)ps_root_unlink(ctx, file);
    595 	else
    596 #else
    597 	UNUSED(ctx);
    598 #endif
    599 		retval = unlink(file);
    600 
    601 	return retval == -1 && errno != ENOENT ? -1 : 0;
    602 }
    603 
    604 int
    605 control_stop(struct dhcpcd_ctx *ctx)
    606 {
    607 	int retval = 0;
    608 	struct fd_list *l;
    609 
    610 	while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) {
    611 		control_free(l);
    612 	}
    613 
    614 #ifdef PRIVSEP
    615 	if (IN_PRIVSEP_SE(ctx)) {
    616 		if (ctx->control_sock[0] != '\0' &&
    617 		    ps_root_unlink(ctx, ctx->control_sock) == -1)
    618 			retval = -1;
    619 		return retval;
    620 	} else if (ctx->options & DHCPCD_FORKED)
    621 		return retval;
    622 #endif
    623 
    624 	if (ctx->control_fd != -1) {
    625 		eloop_event_delete(ctx->eloop, ctx->control_fd);
    626 		close(ctx->control_fd);
    627 		ctx->control_fd = -1;
    628 		if (control_unlink(ctx, ctx->control_sock) == -1)
    629 			retval = -1;
    630 	}
    631 
    632 	return retval;
    633 }
    634 
    635 int
    636 control_open(const char *ifname, sa_family_t family)
    637 {
    638 	struct sockaddr_un sa;
    639 	int fd;
    640 
    641 	if ((fd = make_sock(&sa, ifname, family)) != -1) {
    642 		socklen_t len;
    643 
    644 		len = (socklen_t)SUN_LEN(&sa);
    645 		if (connect(fd, (struct sockaddr *)&sa, len) == -1) {
    646 			close(fd);
    647 			fd = -1;
    648 		}
    649 	}
    650 	return fd;
    651 }
    652 
    653 ssize_t
    654 control_send(struct dhcpcd_ctx *ctx, int argc, char *const *argv)
    655 {
    656 	char buffer[1024];
    657 	int i;
    658 	size_t len, l;
    659 
    660 	if (argc > 255) {
    661 		errno = ENOBUFS;
    662 		return -1;
    663 	}
    664 	len = 0;
    665 	for (i = 0; i < argc; i++) {
    666 		l = strlen(argv[i]) + 1;
    667 		if (len + l > sizeof(buffer)) {
    668 			errno = ENOBUFS;
    669 			return -1;
    670 		}
    671 		memcpy(buffer + len, argv[i], l);
    672 		len += l;
    673 	}
    674 	return write(ctx->control_fd, buffer, len);
    675 }
    676 
    677 ssize_t
    678 control_queuef(struct fd_list *fd, const void *data, size_t data_len,
    679     unsigned int flags)
    680 {
    681 	struct fd_data *d;
    682 	unsigned short events;
    683 
    684 	if (data_len == 0) {
    685 		errno = EINVAL;
    686 		return -1;
    687 	}
    688 
    689 #ifdef CTL_FREE_LIST
    690 	struct fd_data *df;
    691 
    692 	d = NULL;
    693 	TAILQ_FOREACH(df, &fd->free_queue, next) {
    694 		if (d == NULL || d->data_size < df->data_size) {
    695 			d = df;
    696 			if (d->data_size <= data_len)
    697 				break;
    698 		}
    699 	}
    700 	if (d != NULL)
    701 		TAILQ_REMOVE(&fd->free_queue, d, next);
    702 	else
    703 #endif
    704 	{
    705 		d = calloc(1, sizeof(*d));
    706 		if (d == NULL)
    707 			return -1;
    708 	}
    709 
    710 	if (d->data_size == 0)
    711 		d->data = NULL;
    712 	if (d->data_size < data_len) {
    713 		void *nbuf = realloc(d->data, data_len);
    714 		if (nbuf == NULL) {
    715 			free(d->data);
    716 			free(d);
    717 			return -1;
    718 		}
    719 		d->data = nbuf;
    720 		d->data_size = data_len;
    721 	}
    722 	memcpy(d->data, data, data_len);
    723 	d->data_len = data_len;
    724 	d->data_flags = flags;
    725 #ifdef PRIVSEP
    726 	d->data_peer_id = fd->peer_id;
    727 #endif
    728 
    729 	TAILQ_INSERT_TAIL(&fd->queue, d, next);
    730 	events = ELE_WRITE;
    731 	if (fd->flags & FD_LISTEN)
    732 		events |= ELE_READ;
    733 	if (eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data,
    734 		fd) == -1)
    735 		return -1;
    736 	return (ssize_t)d->data_len;
    737 }
    738 
    739 ssize_t
    740 control_queue(struct fd_list *fd, const void *data, size_t data_len)
    741 {
    742 	return control_queuef(fd, data, data_len, FD_DATA_SENDLEN);
    743 }
    744 
    745 int
    746 control_user_ingroup(uid_t uid, gid_t gid, gid_t grpid)
    747 {
    748 #ifdef __APPLE__
    749 	/* why is getgrouplist API is different ..... */
    750 	int *groups = NULL, *gp;
    751 #define GID (int)
    752 #else
    753 	gid_t *groups = NULL, *gp;
    754 #define GID
    755 #endif
    756 	struct passwd *pw;
    757 	int ngroups = 10, err = -1;
    758 
    759 	/* Allow root */
    760 	if (uid == 0)
    761 		return 1;
    762 	if (!GID_SET(grpid))
    763 		return 0;
    764 
    765 	pw = getpwuid(uid);
    766 	if (pw == NULL)
    767 		return 0; /* unknown user is not privileged */
    768 
    769 	groups = reallocarray(groups, (size_t)ngroups, sizeof(*groups));
    770 	if (groups == NULL)
    771 		return -1;
    772 
    773 	if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1) {
    774 		gp = reallocarray(groups, (size_t)ngroups, sizeof(*groups));
    775 		if (gp == NULL)
    776 			goto out;
    777 		groups = gp;
    778 		if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1)
    779 			goto out;
    780 	}
    781 
    782 	for (gp = groups; ngroups != 0; ngroups--, gp++) {
    783 		if (*gp == GID grpid) {
    784 			err = 1;
    785 			goto out;
    786 		}
    787 	}
    788 	err = 0;
    789 
    790 out:
    791 	free(groups);
    792 	return err;
    793 }
    794 
    795 ssize_t
    796 control_handle_listen(struct fd_list *fd)
    797 {
    798 	int err;
    799 
    800 	if (fd->flags & FD_READ) {
    801 		err = 0;
    802 		if (!(fd->flags & FD_COMMAND))
    803 			fd->flags |= FD_LISTEN;
    804 	} else {
    805 		err = errno = EPERM;
    806 		logwarn(__func__);
    807 	}
    808 	return control_queuef(fd, &err, sizeof(err), 0);
    809 }
    810