1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * Privilege Separation for dhcpcd, control proxy
4  * Copyright (c) 2006-2020 Roy Marples <roy@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 <errno.h>
30 #include <signal.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "dhcpcd.h"
35 #include "control.h"
36 #include "eloop.h"
37 #include "logerr.h"
38 #include "privsep.h"
39 
40 #ifdef HAVE_CAPSICUM
41 #include <sys/capsicum.h>
42 #endif
43 
44 static int
45 ps_ctl_startcb(void *arg)
46 {
47 	struct dhcpcd_ctx *ctx = arg;
48 	sa_family_t af;
49 
50 	if (ctx->options & DHCPCD_MASTER) {
51 		setproctitle("[control proxy]");
52 		af = AF_UNSPEC;
53 	} else {
54 		setproctitle("[control proxy] %s%s%s",
55 		    ctx->ifv[0],
56 		    ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
57 		    ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
58 		if ((ctx->options &
59 		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4)
60 			af = AF_INET;
61 		else if ((ctx->options &
62 		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6)
63 			af = AF_INET6;
64 		else
65 			af = AF_UNSPEC;
66 	}
67 
68 	ctx->ps_control_pid = getpid();
69 
70 	return control_start(ctx,
71 	    ctx->options & DHCPCD_MASTER ? NULL : *ctx->ifv, af);
72 }
73 
74 static ssize_t
75 ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg)
76 {
77 	struct dhcpcd_ctx *ctx = arg;
78 
79 	if (psm->ps_cmd != PS_CTL_EOF) {
80 		errno = ENOTSUP;
81 		return -1;
82 	}
83 
84 	if (ctx->ps_control_client != NULL)
85 		ctx->ps_control_client = NULL;
86 	return 0;
87 }
88 
89 static void
90 ps_ctl_recvmsg(void *arg)
91 {
92 	struct dhcpcd_ctx *ctx = arg;
93 
94 	if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1)
95 		logerr(__func__);
96 }
97 
98 static void
99 ps_ctl_signalcb(int sig, void *arg)
100 {
101 	struct dhcpcd_ctx *ctx = arg;
102 
103 	/* Ignore SIGINT, respect PS_STOP command or SIGTERM. */
104 	if (sig == SIGINT)
105 		return;
106 
107 	shutdown(ctx->ps_control_fd, SHUT_RDWR);
108 	eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE);
109 }
110 
111 ssize_t
112 ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len)
113 {
114 
115 	/* Make any change here in dhcpcd.c as well. */
116 	if (strncmp(data, "--version",
117 	    MIN(strlen("--version"), len)) == 0) {
118 		return control_queue(fd, UNCONST(VERSION),
119 		    strlen(VERSION) + 1);
120 	} else if (strncmp(data, "--getconfigfile",
121 	    MIN(strlen("--getconfigfile"), len)) == 0) {
122 		return control_queue(fd, UNCONST(fd->ctx->cffile),
123 		    strlen(fd->ctx->cffile) + 1);
124 	} else if (strncmp(data, "--listen",
125 	    MIN(strlen("--listen"), len)) == 0) {
126 		fd->flags |= FD_LISTEN;
127 		return 0;
128 	}
129 
130 	if (fd->ctx->ps_control_client != NULL &&
131 	    fd->ctx->ps_control_client != fd)
132 	{
133 		logerrx("%s: cannot handle another client", __func__);
134 		return 0;
135 	}
136 	return 1;
137 }
138 
139 static ssize_t
140 ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
141 {
142 	struct dhcpcd_ctx *ctx = arg;
143 	struct iovec *iov = msg->msg_iov;
144 	struct fd_list *fd;
145 	unsigned int fd_flags = FD_SENDLEN;
146 
147 	switch (psm->ps_flags) {
148 	case PS_CTL_PRIV:
149 		break;
150 	case PS_CTL_UNPRIV:
151 		fd_flags |= FD_UNPRIV;
152 		break;
153 	}
154 
155 	switch (psm->ps_cmd) {
156 	case PS_CTL:
157 		if (msg->msg_iovlen != 1) {
158 			errno = EINVAL;
159 			return -1;
160 		}
161 		if (ctx->ps_control_client != NULL) {
162 			logerrx("%s: cannot handle another client", __func__);
163 			return 0;
164 		}
165 		fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags);
166 		if (fd == NULL)
167 			return -1;
168 		ctx->ps_control_client = fd;
169 		control_recvdata(fd, iov->iov_base, iov->iov_len);
170 		break;
171 	case PS_CTL_EOF:
172 		control_free(ctx->ps_control_client);
173 		break;
174 	default:
175 		errno = ENOTSUP;
176 		return -1;
177 	}
178 	return 0;
179 }
180 
181 static void
182 ps_ctl_dodispatch(void *arg)
183 {
184 	struct dhcpcd_ctx *ctx = arg;
185 
186 	if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1)
187 		logerr(__func__);
188 }
189 
190 static void
191 ps_ctl_recv(void *arg)
192 {
193 	struct dhcpcd_ctx *ctx = arg;
194 	char buf[BUFSIZ];
195 	ssize_t len;
196 
197 	errno = 0;
198 	len = read(ctx->ps_control_data_fd, buf, sizeof(buf));
199 	if (len == -1 || len == 0) {
200 		logerr("%s: read", __func__);
201 		eloop_exit(ctx->eloop, EXIT_FAILURE);
202 		return;
203 	}
204 	if (ctx->ps_control_client == NULL) /* client disconnected */
205 		return;
206 	errno = 0;
207 	if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1)
208 		logerr("%s: control_queue", __func__);
209 }
210 
211 static void
212 ps_ctl_listen(void *arg)
213 {
214 	struct dhcpcd_ctx *ctx = arg;
215 	char buf[BUFSIZ];
216 	ssize_t len;
217 	struct fd_list *fd;
218 
219 	errno = 0;
220 	len = read(ctx->ps_control->fd, buf, sizeof(buf));
221 	if (len == -1 || len == 0) {
222 		logerr("%s: read", __func__);
223 		eloop_exit(ctx->eloop, EXIT_FAILURE);
224 		return;
225 	}
226 
227 	/* Send to our listeners */
228 	TAILQ_FOREACH(fd, &ctx->control_fds, next) {
229 		if (!(fd->flags & FD_LISTEN))
230 			continue;
231 		if (control_queue(fd, buf, (size_t)len)== -1)
232 			logerr("%s: control_queue", __func__);
233 	}
234 }
235 
236 pid_t
237 ps_ctl_start(struct dhcpcd_ctx *ctx)
238 {
239 	int data_fd[2], listen_fd[2];
240 	pid_t pid;
241 
242 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1)
243 		return -1;
244 	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1)
245 		return -1;
246 #ifdef PRIVSEP_RIGHTS
247 	if (ps_rights_limit_fdpair(data_fd) == -1)
248 		return -1;
249 	if (ps_rights_limit_fdpair(listen_fd) == -1)
250 		return -1;
251 #endif
252 
253 	pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd,
254 	    ps_ctl_recvmsg, ps_ctl_dodispatch, ctx,
255 	    ps_ctl_startcb, ps_ctl_signalcb,
256 	    PSF_DROPPRIVS);
257 
258 	if (pid == -1)
259 		return -1;
260 	else if (pid != 0) {
261 		ctx->ps_control_data_fd = data_fd[1];
262 		close(data_fd[0]);
263 		ctx->ps_control = control_new(ctx,
264 		    listen_fd[1], FD_SENDLEN | FD_LISTEN);
265 		if (ctx->ps_control == NULL)
266 			return -1;
267 		close(listen_fd[0]);
268 		return pid;
269 	}
270 
271 	ctx->ps_control_data_fd = data_fd[0];
272 	close(data_fd[1]);
273 	if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd,
274 	    ps_ctl_recv, ctx) == -1)
275 		return -1;
276 
277 	ctx->ps_control = control_new(ctx,
278 	    listen_fd[0], 0);
279 	close(listen_fd[1]);
280 	if (ctx->ps_control == NULL)
281 		return -1;
282 	if (eloop_event_add(ctx->eloop, ctx->ps_control->fd,
283 	    ps_ctl_listen, ctx) == -1)
284 		return -1;
285 
286 #ifdef HAVE_CAPSICUM
287 	if (cap_enter() == -1 && errno != ENOSYS)
288 		logerr("%s: cap_enter", __func__);
289 #endif
290 #ifdef HAVE_PLEDGE
291 	if (pledge("stdio inet", NULL) == -1)
292 		logerr("%s: pledge", __func__);
293 #endif
294 	return 0;
295 }
296 
297 int
298 ps_ctl_stop(struct dhcpcd_ctx *ctx)
299 {
300 
301 	return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd);
302 }
303 
304 ssize_t
305 ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len)
306 {
307 	struct dhcpcd_ctx *ctx = fd->ctx;
308 
309 	if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd)
310 		logerrx("%s: cannot deal with another client", __func__);
311 	ctx->ps_control_client = fd;
312 	return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL,
313 	    fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV,
314 	    data, len);
315 }
316 
317 ssize_t
318 ps_ctl_sendeof(struct fd_list *fd)
319 {
320 	struct dhcpcd_ctx *ctx = fd->ctx;
321 
322 	return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0);
323 }
324