1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2021 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23 
24 #include <config.h>
25 
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 
29 #if defined(HAVE_STDINT_H)
30 # include <stdint.h>
31 #elif defined(HAVE_INTTYPES_H)
32 # include <inttypes.h>
33 #endif
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <termios.h>
40 
41 #include "sudo.h"
42 #include "sudo_exec.h"
43 #include "sudo_plugin.h"
44 #include "sudo_plugin_int.h"
45 #include "sudo_rand.h"
46 #include "intercept.pb-c.h"
47 
48 #ifdef _PATH_SUDO_INTERCEPT
49 
50 /* TCSASOFT is a BSD extension that ignores control flags and speed. */
51 # ifndef TCSASOFT
52 #  define TCSASOFT	0
53 # endif
54 
55 enum intercept_state {
56     RECV_HELLO_INITIAL,
57     RECV_HELLO,
58     RECV_SECRET,
59     RECV_POLICY_CHECK,
60     RECV_CONNECTION,
61     POLICY_ACCEPT,
62     POLICY_REJECT,
63     POLICY_ERROR
64 };
65 
66 /* Closure for intercept_cb() */
67 struct intercept_closure {
68     union sudo_token_un token;
69     struct command_details *details;
70     struct sudo_event ev;
71     const char *errstr;
72     char *command;		/* dynamically allocated */
73     char **run_argv;		/* owned by plugin */
74     char **run_envp;		/* dynamically allocated */
75     uint8_t *buf;		/* dynamically allocated */
76     uint32_t len;
77     uint32_t off;
78     int listen_sock;
79     enum intercept_state state;
80 };
81 
82 static union sudo_token_un intercept_token;
83 static in_port_t intercept_listen_port;
84 static void intercept_accept_cb(int fd, int what, void *v);
85 static void intercept_cb(int fd, int what, void *v);
86 
87 bool
intercept_setup(int fd,struct sudo_event_base * evbase,struct command_details * details)88 intercept_setup(int fd, struct sudo_event_base *evbase,
89     struct command_details *details)
90 {
91     struct intercept_closure *closure;
92     debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
93 
94     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
95 	"intercept fd %d\n", fd);
96 
97     closure = calloc(1, sizeof(*closure));
98     if (closure == NULL) {
99 	sudo_warnx("%s", U_("unable to allocate memory"));
100 	goto bad;
101     }
102 
103     /* If we've already seen an InterceptHello, expect a policy check first. */
104     closure->state = sudo_token_isset(intercept_token) ?
105 	RECV_SECRET : RECV_HELLO_INITIAL;
106     closure->details = details;
107     closure->listen_sock = -1;
108 
109     if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
110 	/* This cannot (currently) fail. */
111 	sudo_warn("%s", U_("unable to add event to queue"));
112 	goto bad;
113     }
114     if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
115 	sudo_warn("%s", U_("unable to add event to queue"));
116 	goto bad;
117     }
118 
119     debug_return_bool(true);
120 
121 bad:
122     free(closure);
123     debug_return_bool(false);
124 }
125 
126 /*
127  * Close intercept socket and free closure when we are done with
128  * the connection.
129  */
130 static void
intercept_connection_close(int fd,struct intercept_closure * closure)131 intercept_connection_close(int fd, struct intercept_closure *closure)
132 {
133     size_t n;
134     debug_decl(intercept_connection_close, SUDO_DEBUG_EXEC);
135 
136     sudo_ev_del(NULL, &closure->ev);
137     close(fd);
138     if (closure->listen_sock != -1)
139 	close(closure->listen_sock);
140 
141     free(closure->buf);
142     free(closure->command);
143     if (closure->run_argv != NULL) {
144 	for (n = 0; closure->run_argv[n] != NULL; n++)
145 	    free(closure->run_argv[n]);
146 	free(closure->run_argv);
147     }
148     if (closure->run_envp != NULL) {
149 	for (n = 0; closure->run_envp[n] != NULL; n++)
150 	    free(closure->run_envp[n]);
151 	free(closure->run_envp);
152     }
153     free(closure);
154 
155     debug_return;
156 }
157 
158 /*
159  * Prepare to listen on localhost using an ephemeral port.
160  * Sets intercept_token and intercept_listen_port as side effects.
161  */
162 static bool
prepare_listener(struct intercept_closure * closure)163 prepare_listener(struct intercept_closure *closure)
164 {
165     struct sockaddr_in sin;
166     socklen_t sin_len = sizeof(sin);
167     int sock;
168     debug_decl(prepare_listener, SUDO_DEBUG_EXEC);
169 
170     /* Generate a random token. */
171     do {
172 	arc4random_buf(&intercept_token, sizeof(intercept_token));
173     } while (!sudo_token_isset(intercept_token));
174 
175     /* Create localhost listener socket (currently AF_INET only). */
176     sock = socket(AF_INET, SOCK_STREAM, 0);
177     if (sock == -1) {
178 	sudo_warn("socket");
179 	goto bad;
180     }
181     memset(&sin, 0, sizeof(sin));
182     sin.sin_family = AF_INET;
183     sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
184     sin.sin_port = 0;
185     if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
186 	sudo_warn("bind");
187 	goto bad;
188     }
189     if (getsockname(sock, (struct sockaddr *)&sin, &sin_len) == -1) {
190 	sudo_warn("getsockname");
191 	goto bad;
192     }
193     if (listen(sock, SOMAXCONN) == -1) {
194 	sudo_warn("listen");
195 	goto bad;
196     }
197 
198     closure->listen_sock = sock;
199     intercept_listen_port = ntohs(sin.sin_port);
200     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
201 	"%s: listening on port %hu", __func__, intercept_listen_port);
202 
203     debug_return_bool(true);
204 
205 bad:
206     if (sock != -1)
207 	close(sock);
208     debug_return_bool(false);
209 }
210 
211 /*
212  * Allocate a new command_info[] and update command and runcwd in it.
213  * Fills in cmnd_out with a copy of the command if not NULL.
214  * Returns the new command_info[] which the caller must free.
215  */
216 static char **
update_command_info(char * const * old_command_info,const char * cmnd,const char * runcwd,char ** cmnd_out)217 update_command_info(char * const *old_command_info, const char *cmnd,
218     const char *runcwd, char **cmnd_out)
219 {
220     char **command_info;
221     char * const *oci;
222     size_t n;
223     debug_decl(update_command_info, SUDO_DEBUG_EXEC);
224 
225     /* Rebuild command_info[] with new command and add a runcwd. */
226     for (n = 0; old_command_info[n] != NULL; n++)
227 	continue;
228     command_info = reallocarray(NULL, n + 2, sizeof(char *));
229     if (command_info == NULL) {
230 	goto bad;
231     }
232     for (oci = old_command_info, n = 0; *oci != NULL; oci++) {
233 	const char *cp = *oci;
234 	switch (*cp) {
235 	case 'c':
236 	    if (strncmp(cp, "command=", sizeof("command=") - 1) == 0) {
237 		if (cmnd != NULL) {
238 		    command_info[n] = sudo_new_key_val("command", cmnd);
239 		    if (command_info[n] == NULL) {
240 			goto bad;
241 		    }
242 		    n++;
243 		    continue;
244 		} else if (cmnd_out != NULL) {
245 		    *cmnd_out = strdup(cp + sizeof("command=") - 1);
246 		    if (*cmnd_out == NULL) {
247 			goto bad;
248 		    }
249 		}
250 	    }
251 	    break;
252 	case 'r':
253 	    if (strncmp(cp, "runcwd=", sizeof("runcwd=") - 1) == 0) {
254 		/* Filled in at the end. */
255 		continue;
256 	    }
257 	    break;
258 	}
259 	command_info[n] = strdup(cp);
260 	if (command_info[n] == NULL) {
261 	    goto bad;
262 	}
263 	n++;
264     }
265     /* Append actual runcwd. */
266     command_info[n] = sudo_new_key_val("runcwd", runcwd);
267     if (command_info[n] == NULL) {
268 	goto bad;
269     }
270     n++;
271 
272     command_info[n] = NULL;
273 
274     debug_return_ptr(command_info);
275 bad:
276     if (command_info != NULL) {
277 	for (n = 0; command_info[n] != NULL; n++) {
278 	    free(command_info[n]);
279 	}
280 	free(command_info);
281     }
282     debug_return_ptr(NULL);
283 }
284 
285 static bool
intercept_check_policy(PolicyCheckRequest * req,struct intercept_closure * closure)286 intercept_check_policy(PolicyCheckRequest *req,
287     struct intercept_closure *closure)
288 {
289     char **command_info = NULL;
290     char **command_info_copy = NULL;
291     char **user_env_out = NULL;
292     char **argv = NULL, **run_argv = NULL;
293     bool ret = false;
294     int result;
295     size_t n;
296     debug_decl(intercept_check_policy, SUDO_DEBUG_EXEC);
297 
298     if (req->command == NULL || req->n_argv == 0 || req->n_envp == 0) {
299 	closure->errstr = N_("invalid PolicyCheckRequest");
300 	goto done;
301     }
302 
303     if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
304 	sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
305 	    "req_command: %s", req->command);
306 	for (n = 0; n < req->n_argv; n++) {
307 	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
308 		"req_argv[%zu]: %s", n, req->argv[n]);
309 	}
310     }
311 
312     /* Rebuild argv from PolicyCheckReq so it is NULL-terminated. */
313     argv = reallocarray(NULL, req->n_argv + 1, sizeof(char *));
314     if (argv == NULL) {
315 	closure->errstr = N_("unable to allocate memory");
316 	goto done;
317     }
318     argv[0] = req->command;
319     for (n = 1; n < req->n_argv; n++) {
320 	argv[n] = req->argv[n];
321     }
322     argv[n] = NULL;
323 
324     if (ISSET(closure->details->flags, CD_INTERCEPT)) {
325 	/* We don't currently have a good way to validate the environment. */
326 	sudo_debug_set_active_instance(policy_plugin.debug_instance);
327 	result = policy_plugin.u.policy->check_policy(n, argv, NULL,
328 	    &command_info, &run_argv, &user_env_out, &closure->errstr);
329 	sudo_debug_set_active_instance(sudo_debug_instance);
330 	sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
331 	    "check_policy returns %d", result);
332 
333 	switch (result) {
334 	case 1:
335 	    /* Rebuild command_info[] with runcwd and extract command. */
336 	    command_info_copy = update_command_info(command_info, NULL,
337 		req->cwd ? req->cwd : "unknown", &closure->command);
338 	    if (command_info_copy == NULL) {
339 		closure->errstr = N_("unable to allocate memory");
340 		goto done;
341 	    }
342 	    command_info = command_info_copy;
343 	    closure->state = POLICY_ACCEPT;
344 	    break;
345 	case 0:
346 	    if (closure->errstr == NULL)
347 		closure->errstr = N_("command rejected by policy");
348 	    audit_reject(policy_plugin.name, SUDO_POLICY_PLUGIN,
349 		closure->errstr, command_info);
350 	    closure->state = POLICY_REJECT;
351 	    ret = true;
352 	    goto done;
353 	default:
354 	    goto done;
355 	}
356     } else {
357 	/* No actual policy check, just logging child processes. */
358 	sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
359 	    "not checking policy, audit only");
360 	closure->command = strdup(req->command);
361 	if (closure->command == NULL) {
362 	    closure->errstr = N_("unable to allocate memory");
363 	    goto done;
364 	}
365 
366 	/* Rebuild command_info[] with new command and runcwd. */
367 	command_info = update_command_info(closure->details->info,
368 	    req->command, req->cwd ? req->cwd : "unknown", NULL);
369 	if (command_info == NULL) {
370 	    closure->errstr = N_("unable to allocate memory");
371 	    goto done;
372 	}
373 	closure->state = POLICY_ACCEPT;
374 	run_argv = argv;
375     }
376 
377     if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
378 	sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
379 	    "run_command: %s", closure->command);
380 	for (n = 0; command_info[n] != NULL; n++) {
381 	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
382 		"command_info[%zu]: %s", n, command_info[n]);
383 	}
384 	for (n = 0; run_argv[n] != NULL; n++) {
385 	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
386 		"run_argv[%zu]: %s", n, run_argv[n]);
387 	}
388     }
389 
390     /* run_argv strings may be part of PolicyCheckReq, make a copy. */
391     for (n = 0; run_argv[n] != NULL; n++)
392 	continue;
393     closure->run_argv = reallocarray(NULL, n + 1, sizeof(char *));
394     if (closure->run_argv == NULL) {
395 	closure->errstr = N_("unable to allocate memory");
396 	goto done;
397     }
398     for (n = 0; run_argv[n] != NULL; n++) {
399 	closure->run_argv[n] = strdup(run_argv[n]);
400 	if (closure->run_argv[n] == NULL) {
401 	    closure->errstr = N_("unable to allocate memory");
402 	    goto done;
403 	}
404     }
405     closure->run_argv[n] = NULL;
406 
407     /* envp strings are part of PolicyCheckReq, make a copy. */
408     closure->run_envp = reallocarray(NULL, req->n_envp + 1, sizeof(char *));
409     if (closure->run_envp == NULL) {
410 	closure->errstr = N_("unable to allocate memory");
411 	goto done;
412     }
413     for (n = 0; n < req->n_envp; n++) {
414 	closure->run_envp[n] = strdup(req->envp[n]);
415 	if (closure->run_envp[n] == NULL) {
416 	    closure->errstr = N_("unable to allocate memory");
417 	    goto done;
418 	}
419     }
420     closure->run_envp[n] = NULL;
421 
422     if (ISSET(closure->details->flags, CD_INTERCEPT)) {
423 	audit_accept(policy_plugin.name, SUDO_POLICY_PLUGIN, command_info,
424 		closure->run_argv, closure->run_envp);
425 
426 	/* Call approval plugins and audit the result. */
427 	if (!approval_check(command_info, closure->run_argv, closure->run_envp))
428 	    debug_return_int(0);
429     }
430 
431     /* Audit the event again for the sudo front-end. */
432     audit_accept("sudo", SUDO_FRONT_END, command_info, closure->run_argv,
433 	closure->run_envp);
434 
435     ret = true;
436 
437 done:
438     if (!ret) {
439 	if (closure->errstr == NULL)
440 	    closure->errstr = N_("policy plugin error");
441 	audit_error(policy_plugin.name, SUDO_POLICY_PLUGIN, closure->errstr,
442 	    command_info ? command_info : closure->details->info);
443 	closure->state = POLICY_ERROR;
444     }
445     if (command_info_copy != NULL) {
446 	for (n = 0; command_info_copy[n] != NULL; n++) {
447 	    free(command_info_copy[n]);
448 	}
449 	free(command_info_copy);
450     }
451     free(argv);
452 
453     debug_return_bool(ret);
454 }
455 
456 /*
457  * Read token from sudo_intercept.so and verify w/ intercept_token.
458  * Returns true on success, false on mismatch and -1 on error.
459  */
460 static int
intercept_verify_token(int fd,struct intercept_closure * closure)461 intercept_verify_token(int fd, struct intercept_closure *closure)
462 {
463     ssize_t nread;
464     debug_decl(intercept_read_token, SUDO_DEBUG_EXEC);
465 
466     nread = recv(fd, closure->token.u8 + closure->off,
467 	sizeof(closure->token) - closure->off, 0);
468     switch (nread) {
469     case 0:
470 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
471 	    "EOF reading token");
472 	debug_return_int(false);
473     case -1:
474 	debug_return_int(-1);
475     default:
476 	if (nread + closure->off == sizeof(closure->token))
477 	    break;
478 	/* partial read, update offset and try again */
479 	closure->off += nread;
480 	errno = EAGAIN;
481 	debug_return_int(-1);
482     }
483 
484     closure->off = 0;
485     if (memcmp(&closure->token, &intercept_token, sizeof(closure->token)) != 0) {
486 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
487 	    "token mismatch: got 0x%8x%8x%8x%8x, expected 0x%8x%8x%8x%8x",
488 	    closure->token.u32[3], closure->token.u32[2],
489 	    closure->token.u32[1], closure->token.u32[0],
490 	    intercept_token.u32[3], intercept_token.u32[2],
491 	    intercept_token.u32[1], intercept_token.u32[0]);
492 	debug_return_int(false);
493     }
494     debug_return_int(true);
495 }
496 
497 /*
498  * Read a message from sudo_intercept.so and act on it.
499  */
500 static bool
intercept_read(int fd,struct intercept_closure * closure)501 intercept_read(int fd, struct intercept_closure *closure)
502 {
503     struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
504     InterceptRequest *req = NULL;
505     pid_t saved_pgrp = -1;
506     struct termios oterm;
507     ssize_t nread;
508     bool ret = false;
509     int ttyfd = -1;
510     debug_decl(intercept_read, SUDO_DEBUG_EXEC);
511 
512     if (closure->state == RECV_SECRET) {
513 	switch (intercept_verify_token(fd, closure)) {
514 	case true:
515 	    closure->state = RECV_POLICY_CHECK;
516 	    break;
517 	case false:
518 	    goto done;
519 	default:
520 	    if (errno == EINTR || errno == EAGAIN)
521 		debug_return_bool(true);
522 	    sudo_warn("recv");
523 	    goto done;
524 	}
525     }
526 
527     if (closure->len == 0) {
528 	uint32_t req_len;
529 
530 	/* Read message size (uint32_t in host byte order). */
531 	nread = recv(fd, &req_len, sizeof(req_len), 0);
532 	if (nread != sizeof(req_len)) {
533 	    if (nread == -1) {
534 		if (errno == EINTR || errno == EAGAIN)
535 		    debug_return_bool(true);
536 		sudo_warn("recv");
537 	    }
538 	    goto done;
539 	}
540 
541 	if (req_len == 0) {
542 	    /* zero-length message is possible */
543 	    goto unpack;
544 	}
545 	if (req_len > MESSAGE_SIZE_MAX) {
546 	    sudo_warnx(U_("client request too large: %zu"), (size_t)req_len);
547 	    goto done;
548 	}
549 	if ((closure->buf = malloc(req_len)) == NULL) {
550 	    sudo_warnx("%s", U_("unable to allocate memory"));
551 	    goto done;
552 	}
553 	closure->len = req_len;
554 	sudo_debug_printf(SUDO_DEBUG_INFO, "%s: expecting %u bytes from client",
555 	    __func__, closure->len);
556     }
557 
558     nread = recv(fd, closure->buf + closure->off, closure->len - closure->off,
559 	0);
560     switch (nread) {
561     case 0:
562 	/* EOF, other side must have exited. */
563 	goto done;
564     case -1:
565 	if (errno == EINTR || errno == EAGAIN)
566 	    debug_return_bool(true);
567 	sudo_warn("recv");
568 	goto done;
569     default:
570 	closure->off += nread;
571 	break;
572     }
573     sudo_debug_printf(SUDO_DEBUG_INFO, "%s: received %zd bytes from client",
574 	__func__, nread);
575 
576     if (closure->off != closure->len) {
577 	/* Partial read. */
578 	debug_return_bool(true);
579     }
580 
581 unpack:
582     req = intercept_request__unpack(NULL, closure->len, closure->buf);
583     if (req == NULL) {
584 	sudo_warnx("unable to unpack %s size %zu", "InterceptRequest",
585 	    (size_t)closure->len);
586 	goto done;
587     }
588 
589     sudo_debug_printf(SUDO_DEBUG_INFO,
590 	"%s: finished receiving %u bytes from client", __func__, closure->len);
591     sudo_ev_del(evbase, &closure->ev);
592     free(closure->buf);
593     closure->buf = NULL;
594     closure->len = 0;
595     closure->off = 0;
596 
597     switch (req->type_case) {
598     case INTERCEPT_REQUEST__TYPE_POLICY_CHECK_REQ:
599 	if (closure->state != RECV_POLICY_CHECK) {
600 	    /* Only a single policy check request is allowed. */
601 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
602 		"state mismatch, expected RECV_POLICY_CHECK (%d), got %d",
603 		RECV_POLICY_CHECK, closure->state);
604 	    goto done;
605 	}
606 
607 	/* Take back control of the tty, if necessary, for the policy check. */
608 	ttyfd = open(_PATH_TTY, O_RDWR);
609 	if (ttyfd != -1) {
610 	    saved_pgrp = tcgetpgrp(ttyfd);
611 	    if (saved_pgrp == -1 || tcsetpgrp(ttyfd, getpgid(0)) == -1 ||
612 		    tcgetattr(ttyfd, &oterm) == -1) {
613 		close(ttyfd);
614 		ttyfd = -1;
615 	    }
616 	}
617 
618 	ret = intercept_check_policy(req->u.policy_check_req, closure);
619 
620 	/* We must restore tty before any error handling. */
621 	if (ttyfd != -1) {
622 	    (void)tcsetattr(ttyfd, TCSASOFT|TCSAFLUSH, &oterm);
623 	    (void)tcsetpgrp(ttyfd, saved_pgrp);
624 	}
625 	if (!ret)
626 	    goto done;
627 	break;
628     case INTERCEPT_REQUEST__TYPE_HELLO:
629 	switch (closure->state) {
630 	case RECV_HELLO_INITIAL:
631 	    if (!prepare_listener(closure))
632 		goto done;
633 	    break;
634 	case RECV_HELLO:
635 	    break;
636 	default:
637 	    /* Only accept hello on a socket with an accepted command. */
638 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
639 		"got InterceptHello without an accepted command");
640 	    goto done;
641 	}
642 	break;
643     default:
644 	sudo_warnx(U_("unexpected type_case value %d in %s from %s"),
645 	    req->type_case, "InterceptRequest", "sudo_intercept.so");
646 	goto done;
647     }
648 
649     /* Switch event to write mode for the reply. */
650     if (sudo_ev_set(&closure->ev, fd, SUDO_EV_WRITE|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
651 	/* This cannot (currently) fail. */
652 	sudo_warn("%s", U_("unable to add event to queue"));
653 	goto done;
654     }
655     if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
656 	sudo_warn("%s", U_("unable to add event to queue"));
657 	goto done;
658     }
659 
660     ret = true;
661 
662 done:
663     if (ttyfd != -1)
664 	close(ttyfd);
665     intercept_request__free_unpacked(req, NULL);
666     debug_return_bool(ret);
667 }
668 
669 static bool
fmt_intercept_response(InterceptResponse * resp,struct intercept_closure * closure)670 fmt_intercept_response(InterceptResponse *resp,
671     struct intercept_closure *closure)
672 {
673     uint32_t resp_len;
674     bool ret = false;
675     debug_decl(fmt_intercept_response, SUDO_DEBUG_EXEC);
676 
677     closure->len = intercept_response__get_packed_size(resp);
678     if (closure->len > MESSAGE_SIZE_MAX) {
679 	sudo_warnx(U_("server message too large: %zu"), (size_t)closure->len);
680 	goto done;
681     }
682 
683     /* Wire message size is used for length encoding, precedes message. */
684     resp_len = closure->len;
685     closure->len += sizeof(resp_len);
686 
687     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
688 	"size + InterceptResponse %zu bytes", (size_t)closure->len);
689 
690     if ((closure->buf = malloc(closure->len)) == NULL) {
691 	sudo_warnx("%s", U_("unable to allocate memory"));
692 	goto done;
693     }
694     memcpy(closure->buf, &resp_len, sizeof(resp_len));
695     intercept_response__pack(resp, closure->buf + sizeof(resp_len));
696 
697     ret = true;
698 
699 done:
700     debug_return_bool(ret);
701 }
702 
703 static bool
fmt_hello_response(struct intercept_closure * closure)704 fmt_hello_response(struct intercept_closure *closure)
705 {
706     HelloResponse hello_resp = HELLO_RESPONSE__INIT;
707     InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
708     debug_decl(fmt_hello_response, SUDO_DEBUG_EXEC);
709 
710     hello_resp.portno = intercept_listen_port;
711     hello_resp.token_lo = intercept_token.u64[0];
712     hello_resp.token_hi = intercept_token.u64[1];
713 
714     resp.u.hello_resp = &hello_resp;
715     resp.type_case = INTERCEPT_RESPONSE__TYPE_HELLO_RESP;
716 
717     debug_return_bool(fmt_intercept_response(&resp, closure));
718 }
719 
720 static bool
fmt_accept_message(struct intercept_closure * closure)721 fmt_accept_message(struct intercept_closure *closure)
722 {
723     PolicyAcceptMessage msg = POLICY_ACCEPT_MESSAGE__INIT;
724     InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
725     size_t n;
726     debug_decl(fmt_accept_message, SUDO_DEBUG_EXEC);
727 
728     msg.run_command = closure->command;
729     msg.run_argv = closure->run_argv;
730     for (n = 0; closure->run_argv[n] != NULL; n++)
731 	continue;
732     msg.n_run_argv = n;
733     msg.run_envp = closure->run_envp;
734     for (n = 0; closure->run_envp[n] != NULL; n++)
735 	continue;
736     msg.n_run_envp = n;
737 
738     resp.u.accept_msg = &msg;
739     resp.type_case = INTERCEPT_RESPONSE__TYPE_ACCEPT_MSG;
740 
741     debug_return_bool(fmt_intercept_response(&resp, closure));
742 }
743 
744 static bool
fmt_reject_message(struct intercept_closure * closure)745 fmt_reject_message(struct intercept_closure *closure)
746 {
747     PolicyRejectMessage msg = POLICY_REJECT_MESSAGE__INIT;
748     InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
749     debug_decl(fmt_reject_message, SUDO_DEBUG_EXEC);
750 
751     msg.reject_message = (char *)closure->errstr;
752 
753     resp.u.reject_msg = &msg;
754     resp.type_case = INTERCEPT_RESPONSE__TYPE_REJECT_MSG;
755 
756     debug_return_bool(fmt_intercept_response(&resp, closure));
757 }
758 
759 static bool
fmt_error_message(struct intercept_closure * closure)760 fmt_error_message(struct intercept_closure *closure)
761 {
762     PolicyErrorMessage msg = POLICY_ERROR_MESSAGE__INIT;
763     InterceptResponse resp = INTERCEPT_RESPONSE__INIT;
764     debug_decl(fmt_error_message, SUDO_DEBUG_EXEC);
765 
766     msg.error_message = (char *)closure->errstr;
767 
768     resp.u.error_msg = &msg;
769     resp.type_case = INTERCEPT_RESPONSE__TYPE_ERROR_MSG;
770 
771     debug_return_bool(fmt_intercept_response(&resp, closure));
772 }
773 
774 /*
775  * Write a response to sudo_intercept.so.
776  */
777 static bool
intercept_write(int fd,struct intercept_closure * closure)778 intercept_write(int fd, struct intercept_closure *closure)
779 {
780     struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
781     ssize_t nwritten;
782     bool ret = false;
783     debug_decl(intercept_write, SUDO_DEBUG_EXEC);
784 
785     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, "state %d",
786 	closure->state);
787 
788     if (closure->len == 0) {
789 	/* Format new message. */
790 	switch (closure->state) {
791 	    case RECV_HELLO_INITIAL:
792 	    case RECV_HELLO:
793 		if (!fmt_hello_response(closure))
794 		    goto done;
795 		break;
796 	    case POLICY_ACCEPT:
797 		if (!fmt_accept_message(closure))
798 		    goto done;
799 		break;
800 	    case POLICY_REJECT:
801 		if (!fmt_reject_message(closure))
802 		    goto done;
803 		break;
804 	    default:
805 		if (!fmt_error_message(closure))
806 		    goto done;
807 		break;
808 	}
809     }
810 
811     sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sending %u bytes to client",
812 	__func__, closure->len - closure->off);
813     nwritten = send(fd, closure->buf + closure->off,
814 	closure->len - closure->off, 0);
815     if (nwritten == -1) {
816 	if (errno == EINTR || errno == EAGAIN)
817 	    debug_return_bool(true);
818 	sudo_warn("send");
819 	goto done;
820     }
821     closure->off += nwritten;
822 
823     if (closure->off != closure->len) {
824 	/* Partial write. */
825 	debug_return_bool(true);
826     }
827 
828     sudo_debug_printf(SUDO_DEBUG_INFO, "%s: sent %u bytes to client",
829 	__func__, closure->len);
830     sudo_ev_del(evbase, &closure->ev);
831     free(closure->buf);
832     closure->buf = NULL;
833     closure->len = 0;
834     closure->off = 0;
835 
836     switch (closure->state) {
837     case RECV_HELLO_INITIAL:
838 	/* Re-use event for the listener. */
839 	close(fd);
840 	if (sudo_ev_set(&closure->ev, closure->listen_sock, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_accept_cb, closure) == -1) {
841 	    /* This cannot (currently) fail. */
842 	    sudo_warn("%s", U_("unable to add event to queue"));
843 	    goto done;
844 	}
845 	if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
846 	    sudo_warn("%s", U_("unable to add event to queue"));
847 	    goto done;
848 	}
849 	closure->listen_sock = -1;
850 	closure->state = RECV_CONNECTION;
851 	break;
852     case POLICY_ACCEPT:
853 	/* Re-use event to read InterceptHello from sudo_intercept.so ctor. */
854 	if (sudo_ev_set(&closure->ev, fd, SUDO_EV_READ|SUDO_EV_PERSIST, intercept_cb, closure) == -1) {
855 	    /* This cannot (currently) fail. */
856 	    sudo_warn("%s", U_("unable to add event to queue"));
857 	    goto done;
858 	}
859 	if (sudo_ev_add(evbase, &closure->ev, NULL, false) == -1) {
860 	    sudo_warn("%s", U_("unable to add event to queue"));
861 	    goto done;
862 	}
863 	closure->state = RECV_HELLO;
864 	break;
865     default:
866 	/* Done with this connection. */
867 	intercept_connection_close(fd, closure);
868     }
869 
870     ret = true;
871 
872 done:
873     debug_return_bool(ret);
874 }
875 
876 static void
intercept_cb(int fd,int what,void * v)877 intercept_cb(int fd, int what, void *v)
878 {
879     struct intercept_closure *closure = v;
880     bool success = false;
881     debug_decl(intercept_cb, SUDO_DEBUG_EXEC);
882 
883     switch (what) {
884     case SUDO_EV_READ:
885 	success = intercept_read(fd, closure);
886 	break;
887     case SUDO_EV_WRITE:
888 	success = intercept_write(fd, closure);
889 	break;
890     default:
891 	sudo_warnx("%s: unexpected event type %d", __func__, what);
892 	break;
893     }
894 
895     if (!success)
896 	intercept_connection_close(fd, closure);
897 
898     debug_return;
899 }
900 
901 /*
902  * Accept a new connection from the client and fill in a client closure.
903  * Registers a new event for the connection.
904  */
905 static void
intercept_accept_cb(int fd,int what,void * v)906 intercept_accept_cb(int fd, int what, void *v)
907 {
908     struct intercept_closure *closure = v;
909     struct sudo_event_base *evbase = sudo_ev_get_base(&closure->ev);
910     struct sockaddr_in sin;
911     socklen_t sin_len = sizeof(sin);
912     int client_sock, flags;
913     debug_decl(intercept_accept_cb, SUDO_DEBUG_EXEC);
914 
915     if (closure->state != RECV_CONNECTION) {
916 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
917 	    "state mismatch, expected RECV_CONNECTION (%d), got %d",
918 	    RECV_CONNECTION, closure->state);
919 	intercept_connection_close(fd, closure);
920 	debug_return;
921     }
922 
923     client_sock = accept(fd, (struct sockaddr *)&sin, &sin_len);
924     if (client_sock == -1) {
925 	sudo_warn("accept");
926 	goto bad;
927     }
928     flags = fcntl(client_sock, F_GETFL, 0);
929     if (flags != -1)
930 	(void)fcntl(client_sock, F_SETFL, flags | O_NONBLOCK);
931 
932     if (!intercept_setup(client_sock, evbase, closure->details)) {
933 	goto bad;
934     }
935 
936     debug_return;
937 
938 bad:
939     if (client_sock != -1)
940 	close(client_sock);
941     debug_return;
942 }
943 #else /* _PATH_SUDO_INTERCEPT */
944 bool
intercept_setup(int fd,struct sudo_event_base * evbase,struct command_details * details)945 intercept_setup(int fd, struct sudo_event_base *evbase,
946     struct command_details *details)
947 {
948     debug_decl(intercept_setup, SUDO_DEBUG_EXEC);
949 
950     /* Intercept support not compiled in. */
951 
952     debug_return_bool(false);
953 }
954 #endif /* _PATH_SUDO_INTERCEPT */
955