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