/src/qubes-os/qubes-core-qrexec/daemon/qrexec-daemon.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The Qubes OS Project, http://www.qubes-os.org |
3 | | * |
4 | | * Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version 2 |
9 | | * of the License, or (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | | * |
20 | | */ |
21 | | |
22 | | #include <stdio.h> |
23 | | #include <stdlib.h> |
24 | | #include <unistd.h> |
25 | | #include <signal.h> |
26 | | #include <errno.h> |
27 | | #include <fcntl.h> |
28 | | #include <sys/stat.h> |
29 | | #include <sys/wait.h> |
30 | | #include <sys/socket.h> |
31 | | #include <sys/un.h> |
32 | | #include <err.h> |
33 | | #include <string.h> |
34 | | #include <assert.h> |
35 | | #include <getopt.h> |
36 | | #include "qrexec.h" |
37 | | #include "libqrexec-utils.h" |
38 | | #include "../libqrexec/ioall.h" |
39 | | |
40 | 0 | #define QREXEC_MIN_VERSION QREXEC_PROTOCOL_V2 |
41 | 0 | #define QREXEC_SOCKET_PATH "/run/qubes/policy.sock" |
42 | | |
43 | | #ifdef COVERAGE |
44 | | void __gcov_dump(void); |
45 | | void __gcov_reset(void); |
46 | | #endif |
47 | | |
48 | | enum client_state { |
49 | | CLIENT_INVALID = 0, // table slot not used |
50 | | CLIENT_HELLO, // waiting for client hello |
51 | | CLIENT_CMDLINE, // waiting for cmdline from client |
52 | | CLIENT_RUNNING // waiting for client termination (to release vchan port) |
53 | | }; |
54 | | |
55 | | enum vchan_port_state { |
56 | | VCHAN_PORT_UNUSED = -1 |
57 | | }; |
58 | | |
59 | | struct _client { |
60 | | int state; // enum client_state |
61 | | }; |
62 | | |
63 | | enum policy_response { |
64 | | RESPONSE_PENDING, |
65 | | RESPONSE_ALLOW, |
66 | | RESPONSE_DENY, |
67 | | RESPONSE_MALFORMED, |
68 | | }; |
69 | | |
70 | | struct _policy_pending { |
71 | | pid_t pid; |
72 | | struct service_params params; |
73 | | enum policy_response response_sent; |
74 | | }; |
75 | | |
76 | 329 | #define VCHAN_BASE_DATA_PORT (VCHAN_BASE_PORT+1) |
77 | | |
78 | | /* |
79 | | The "clients" array is indexed by client's fd. |
80 | | Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity. |
81 | | */ |
82 | | |
83 | 6.89k | #define MAX_CLIENTS MAX_FDS |
84 | | static struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections |
85 | | |
86 | | static struct _policy_pending policy_pending[MAX_CLIENTS]; |
87 | | static int policy_pending_max = -1; |
88 | | |
89 | | /* indexed with vchan port number relative to VCHAN_BASE_DATA_PORT; stores |
90 | | * either VCHAN_PORT_* or remote domain id for used port */ |
91 | | static int used_vchan_ports[MAX_CLIENTS]; |
92 | | |
93 | | /* notify client (close its connection) when connection initiated by it was |
94 | | * terminated - used by qrexec-policy to cleanup (disposable) VM; indexed with |
95 | | * vchan port number relative to VCHAN_BASE_DATA_PORT; stores fd of given |
96 | | * client or -1 if none requested */ |
97 | | static int vchan_port_notify_client[MAX_CLIENTS]; |
98 | | |
99 | | static int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table |
100 | | static int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor |
101 | | static const char *default_user = "user"; |
102 | | static const char default_user_keyword[] = "DEFAULT:"; |
103 | 0 | #define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2) |
104 | | |
105 | | static int opt_quiet = 0; |
106 | | static int opt_direct = 0; |
107 | | |
108 | | static const char *socket_dir = QREXEC_DAEMON_SOCKET_DIR; |
109 | | static const char *policy_program = QREXEC_POLICY_PROGRAM; |
110 | | |
111 | | #ifdef __GNUC__ |
112 | | # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) |
113 | | #else |
114 | | # define UNUSED(x) UNUSED_ ## x |
115 | | #endif |
116 | | |
117 | | static volatile int children_count; |
118 | | static volatile int child_exited; |
119 | | static volatile int terminate_requested; |
120 | | |
121 | | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
122 | | #include "../fuzz/fuzz.h" |
123 | | #else |
124 | | static |
125 | | #endif |
126 | | libvchan_t *vchan; |
127 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
128 | | static |
129 | | #endif |
130 | | int protocol_version; |
131 | | |
132 | | static void sigusr1_handler(int UNUSED(x)) |
133 | 0 | { |
134 | 0 | if (!opt_quiet) |
135 | 0 | LOG(INFO, "Connected to VM"); |
136 | 0 | exit(0); |
137 | 0 | } |
138 | | |
139 | | static void sigchld_parent_handler(int UNUSED(x)) |
140 | 0 | { |
141 | 0 | children_count--; |
142 | | /* starting value is 0 so we see dead real qrexec-daemon as -1 */ |
143 | 0 | if (children_count < 0) { |
144 | 0 | LOG(ERROR, "Connection to the VM failed"); |
145 | 0 | exit(1); |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | |
150 | | static char *remote_domain_name; // guess what |
151 | | static int remote_domain_id; |
152 | | |
153 | | static void unlink_qrexec_socket(void) |
154 | 0 | { |
155 | 0 | char socket_address[40]; |
156 | 0 | char link_to_socket_name[strlen(remote_domain_name) + sizeof(socket_address)]; |
157 | |
|
158 | 0 | int v = snprintf(socket_address, sizeof(socket_address), |
159 | 0 | "%s/qrexec.%d", socket_dir, remote_domain_id); |
160 | 0 | if (v < (int)sizeof("/qrexec.1") || v >= (int)sizeof(socket_address)) |
161 | 0 | abort(); |
162 | 0 | v = snprintf(link_to_socket_name, sizeof(link_to_socket_name), |
163 | 0 | "%s/qrexec.%s", socket_dir, remote_domain_name); |
164 | 0 | if (v < (int)sizeof("/qrexec.") || v >= (int)sizeof(link_to_socket_name)) |
165 | 0 | abort(); |
166 | 0 | unlink(socket_address); |
167 | 0 | unlink(link_to_socket_name); |
168 | 0 | } |
169 | | |
170 | | static void handle_vchan_error(const char *op) |
171 | 89 | { |
172 | 89 | LOG(ERROR, "Error while vchan %s, exiting", op); |
173 | 89 | exit(1); |
174 | 89 | } |
175 | | |
176 | | |
177 | | static int create_qrexec_socket(int domid, const char *domname) |
178 | 0 | { |
179 | 0 | char socket_address[40]; |
180 | 0 | char link_to_socket_name[strlen(domname) + sizeof(socket_address)]; |
181 | |
|
182 | 0 | snprintf(socket_address, sizeof(socket_address), |
183 | 0 | "%s/qrexec.%d", socket_dir, domid); |
184 | 0 | snprintf(link_to_socket_name, sizeof link_to_socket_name, |
185 | 0 | "%s/qrexec.%s", socket_dir, domname); |
186 | 0 | unlink(link_to_socket_name); |
187 | | |
188 | | /* When running as root, make the socket accessible; perms on /var/run/qubes still apply */ |
189 | 0 | umask(0); |
190 | 0 | if (symlink(socket_address, link_to_socket_name)) { |
191 | 0 | PERROR("symlink(%s,%s)", socket_address, link_to_socket_name); |
192 | 0 | } |
193 | 0 | int fd = get_server_socket(socket_address); |
194 | 0 | umask(0077); |
195 | 0 | return fd; |
196 | 0 | } |
197 | | |
198 | 0 | #define MAX_STARTUP_TIME_DEFAULT 60 |
199 | | |
200 | | static void incompatible_protocol_error_message( |
201 | | const char *domain_name, int remote_version) |
202 | 0 | { |
203 | 0 | char text[1024]; |
204 | 0 | int ret; |
205 | 0 | struct stat buf; |
206 | 0 | ret=stat("/usr/bin/kdialog", &buf); |
207 | 0 | #define KDIALOG_CMD "kdialog --title 'Qrexec daemon' --sorry " |
208 | 0 | #define ZENITY_CMD "zenity --title 'Qrexec daemon' --warning --text " |
209 | 0 | snprintf(text, sizeof(text), |
210 | 0 | "%s" |
211 | 0 | "'Domain %s uses incompatible qrexec protocol (%d instead of %d). " |
212 | 0 | "You need to update either dom0 or VM packages.\n" |
213 | 0 | "To access this VM console do not close this error message and run:\n" |
214 | 0 | "sudo xl console -t pv %s'", |
215 | 0 | ret==0 ? KDIALOG_CMD : ZENITY_CMD, |
216 | 0 | domain_name, remote_version, QREXEC_PROTOCOL_VERSION, domain_name); |
217 | 0 | #undef KDIALOG_CMD |
218 | 0 | #undef ZENITY_CMD |
219 | | /* silence -Wunused-result */ |
220 | 0 | ret = system(text); |
221 | 0 | } |
222 | | |
223 | | static int handle_agent_hello(libvchan_t *ctrl, const char *domain_name) |
224 | 0 | { |
225 | 0 | struct msg_header hdr; |
226 | 0 | struct peer_info info; |
227 | 0 | int actual_version; |
228 | |
|
229 | 0 | if (libvchan_recv(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
230 | 0 | LOG(ERROR, "Failed to read agent HELLO hdr"); |
231 | 0 | return -1; |
232 | 0 | } |
233 | | |
234 | 0 | if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) { |
235 | 0 | LOG(ERROR, "Invalid HELLO packet received: type %d, len %d", hdr.type, hdr.len); |
236 | 0 | return -1; |
237 | 0 | } |
238 | | |
239 | 0 | if (libvchan_recv(ctrl, &info, sizeof(info)) != sizeof(info)) { |
240 | 0 | LOG(ERROR, "Failed to read agent HELLO body"); |
241 | 0 | return -1; |
242 | 0 | } |
243 | | |
244 | 0 | actual_version = info.version < QREXEC_PROTOCOL_VERSION ? info.version : QREXEC_PROTOCOL_VERSION; |
245 | |
|
246 | 0 | if (actual_version < QREXEC_MIN_VERSION) { |
247 | 0 | LOG(ERROR, "Incompatible agent protocol version (remote %d, local %d)", info.version, QREXEC_PROTOCOL_VERSION); |
248 | 0 | incompatible_protocol_error_message(domain_name, info.version); |
249 | 0 | return -1; |
250 | 0 | } |
251 | | |
252 | | /* send own HELLO */ |
253 | | /* those messages are the same as received from agent, but set it again for |
254 | | * readability */ |
255 | 0 | hdr.type = MSG_HELLO; |
256 | 0 | hdr.len = sizeof(info); |
257 | 0 | info.version = QREXEC_PROTOCOL_VERSION; |
258 | |
|
259 | 0 | if (libvchan_send(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
260 | 0 | LOG(ERROR, "Failed to send HELLO hdr to agent"); |
261 | 0 | return -1; |
262 | 0 | } |
263 | | |
264 | 0 | if (libvchan_send(ctrl, &info, sizeof(info)) != sizeof(info)) { |
265 | 0 | LOG(ERROR, "Failed to send HELLO hdr to agent"); |
266 | 0 | return -1; |
267 | 0 | } |
268 | | |
269 | 0 | return actual_version; |
270 | 0 | } |
271 | | |
272 | | static void signal_handler(int sig); |
273 | | |
274 | | /* do the preparatory tasks, needed before entering the main event loop */ |
275 | | static void init(int xid) |
276 | 0 | { |
277 | 0 | char qrexec_error_log_name[256]; |
278 | 0 | int logfd; |
279 | 0 | int i; |
280 | 0 | pid_t pid; |
281 | 0 | int startup_timeout = MAX_STARTUP_TIME_DEFAULT; |
282 | 0 | const char *startup_timeout_str = NULL; |
283 | |
|
284 | 0 | if (xid <= 0) { |
285 | 0 | LOG(ERROR, "domain id=0?"); |
286 | 0 | exit(1); |
287 | 0 | } |
288 | 0 | startup_timeout_str = getenv("QREXEC_STARTUP_TIMEOUT"); |
289 | 0 | if (startup_timeout_str) { |
290 | 0 | startup_timeout = atoi(startup_timeout_str); |
291 | 0 | if (startup_timeout <= 0) |
292 | | // invalid or negative number |
293 | 0 | startup_timeout = MAX_STARTUP_TIME_DEFAULT; |
294 | 0 | } |
295 | |
|
296 | 0 | if (!opt_direct) { |
297 | 0 | struct sigaction action = { |
298 | 0 | .sa_handler = sigusr1_handler, |
299 | 0 | .sa_flags = 0, |
300 | 0 | }; |
301 | 0 | if (sigaction(SIGUSR1, &action, NULL)) |
302 | 0 | err(1, "sigaction"); |
303 | 0 | action.sa_handler = sigchld_parent_handler; |
304 | 0 | if (sigaction(SIGCHLD, &action, NULL)) |
305 | 0 | err(1, "sigaction"); |
306 | 0 | switch (pid=fork()) { |
307 | 0 | case -1: |
308 | 0 | PERROR("fork"); |
309 | 0 | exit(1); |
310 | 0 | case 0: |
311 | 0 | break; |
312 | 0 | default: |
313 | 0 | if (getenv("QREXEC_STARTUP_NOWAIT")) |
314 | 0 | exit(0); |
315 | 0 | if (!opt_quiet) |
316 | 0 | LOG(ERROR, "Waiting for VM's qrexec agent."); |
317 | 0 | for (i=0;i<startup_timeout;i++) { |
318 | 0 | sleep(1); |
319 | 0 | if (!opt_quiet) |
320 | 0 | fprintf(stderr, "."); |
321 | 0 | if (i==startup_timeout-1) { |
322 | 0 | break; |
323 | 0 | } |
324 | 0 | } |
325 | 0 | LOG(ERROR, "Cannot connect to '%s' qrexec agent for %d seconds, giving up", remote_domain_name, startup_timeout); |
326 | 0 | exit(3); |
327 | 0 | } |
328 | 0 | } |
329 | | |
330 | 0 | close(0); |
331 | |
|
332 | 0 | if (!opt_direct) { |
333 | 0 | snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name), |
334 | 0 | "/var/log/qubes/qrexec.%s.log", remote_domain_name); |
335 | 0 | umask(0007); // make the log readable by the "qubes" group |
336 | 0 | logfd = |
337 | 0 | open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC, |
338 | 0 | 0660); |
339 | |
|
340 | 0 | if (logfd < 0) { |
341 | 0 | PERROR("open"); |
342 | 0 | exit(1); |
343 | 0 | } |
344 | |
|
345 | 0 | dup2(logfd, 1); |
346 | 0 | dup2(logfd, 2); |
347 | |
|
348 | 0 | if (chdir("/var/run/qubes") < 0) { |
349 | 0 | PERROR("chdir /var/run/qubes failed"); |
350 | 0 | exit(1); |
351 | 0 | } |
352 | 0 | if (setsid() < 0) { |
353 | 0 | PERROR("setsid()"); |
354 | 0 | exit(1); |
355 | 0 | } |
356 | 0 | } |
357 | |
|
358 | 0 | vchan = libvchan_client_init(xid, VCHAN_BASE_PORT); |
359 | 0 | if (!vchan) { |
360 | 0 | PERROR("cannot connect to qrexec agent"); |
361 | 0 | exit(1); |
362 | 0 | } |
363 | |
|
364 | 0 | protocol_version = handle_agent_hello(vchan, remote_domain_name); |
365 | 0 | if (protocol_version < 0) { |
366 | 0 | exit(1); |
367 | 0 | } |
368 | |
|
369 | 0 | if (setgid(getgid()) < 0) { |
370 | 0 | PERROR("setgid()"); |
371 | 0 | exit(1); |
372 | 0 | } |
373 | 0 | if (setuid(getuid()) < 0) { |
374 | 0 | PERROR("setuid()"); |
375 | 0 | exit(1); |
376 | 0 | } |
377 | | |
378 | | /* initialize clients state arrays */ |
379 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
380 | 0 | clients[i].state = CLIENT_INVALID; |
381 | 0 | policy_pending[i].pid = 0; |
382 | 0 | used_vchan_ports[i] = VCHAN_PORT_UNUSED; |
383 | 0 | vchan_port_notify_client[i] = VCHAN_PORT_UNUSED; |
384 | 0 | } |
385 | |
|
386 | 0 | atexit(unlink_qrexec_socket); |
387 | 0 | qrexec_daemon_unix_socket_fd = |
388 | 0 | create_qrexec_socket(xid, remote_domain_name); |
389 | |
|
390 | 0 | struct sigaction sigchld_action = { |
391 | 0 | .sa_handler = signal_handler, |
392 | 0 | .sa_flags = SA_NOCLDSTOP, |
393 | 0 | }; |
394 | 0 | struct sigaction sigterm_action = { |
395 | 0 | .sa_handler = signal_handler, |
396 | 0 | .sa_flags = 0, |
397 | 0 | }; |
398 | 0 | if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) |
399 | 0 | err(1, "signal"); |
400 | 0 | if (sigaction(SIGCHLD, &sigchld_action, NULL)) |
401 | 0 | err(1, "sigaction"); |
402 | 0 | if (signal(SIGUSR1, SIG_DFL) == SIG_ERR) |
403 | 0 | err(1, "signal"); |
404 | 0 | if (sigaction(SIGTERM, &sigterm_action, NULL)) |
405 | 0 | err(1, "sigaction"); |
406 | 0 | if (!opt_direct) |
407 | 0 | kill(getppid(), SIGUSR1); // let the parent know we are ready |
408 | 0 | } |
409 | | |
410 | | static int send_client_hello(int fd) |
411 | 0 | { |
412 | 0 | struct msg_header hdr; |
413 | 0 | struct peer_info info; |
414 | |
|
415 | 0 | hdr.type = MSG_HELLO; |
416 | 0 | hdr.len = sizeof(info); |
417 | 0 | info.version = QREXEC_PROTOCOL_VERSION; |
418 | |
|
419 | 0 | if (!write_all(fd, &hdr, sizeof(hdr))) { |
420 | 0 | LOG(ERROR, "Failed to send MSG_HELLO hdr to client %d", fd); |
421 | 0 | return -1; |
422 | 0 | } |
423 | 0 | if (!write_all(fd, &info, sizeof(info))) { |
424 | 0 | LOG(ERROR, "Failed to send MSG_HELLO to client %d", fd); |
425 | 0 | return -1; |
426 | 0 | } |
427 | 0 | return 0; |
428 | 0 | } |
429 | | |
430 | | static int allocate_vchan_port(int connect_domain) |
431 | 0 | { |
432 | | /* |
433 | | Make sure the allocated ports numbers are unique for a given {domX, domY} |
434 | | set. |
435 | | |
436 | | For domX-domY connections, both daemons can allocate ports. If they both |
437 | | allocate the same port number, this can cause trouble: |
438 | | - We might receive MSG_CONNECTION_TERMINATED for the wrong connection. |
439 | | - Although vchan connections in both directions can exist independently, |
440 | | the direction (client-server or server-client) is not always |
441 | | the same, so collision is still possible. |
442 | | |
443 | | To prevent that from happening, for X < Y allow the daemon for X to |
444 | | allocate only odd port numbers, and the daemon for Y to allocate only |
445 | | even port numbers. |
446 | | |
447 | | (This does not apply if we are connecting to/from dom0, as there is no |
448 | | separate daemon running for dom0). |
449 | | */ |
450 | |
|
451 | 0 | int i, step; |
452 | 0 | if (connect_domain == 0) { |
453 | 0 | i = 0; |
454 | 0 | step = 1; |
455 | 0 | } else { |
456 | 0 | i = connect_domain > remote_domain_id ? 1 : 0; |
457 | 0 | step = 2; |
458 | 0 | } |
459 | |
|
460 | 0 | for (; i < MAX_CLIENTS; i += step) { |
461 | 0 | if (used_vchan_ports[i] == VCHAN_PORT_UNUSED) { |
462 | 0 | used_vchan_ports[i] = connect_domain; |
463 | 0 | return VCHAN_BASE_DATA_PORT+i; |
464 | 0 | } |
465 | 0 | } |
466 | 0 | return 0; |
467 | 0 | } |
468 | | |
469 | | static void handle_new_client(void) |
470 | 0 | { |
471 | 0 | int fd = do_accept(qrexec_daemon_unix_socket_fd); |
472 | 0 | if (fd >= MAX_CLIENTS) { |
473 | 0 | LOG(ERROR, "too many clients ?"); |
474 | 0 | exit(1); |
475 | 0 | } |
476 | |
|
477 | 0 | if (send_client_hello(fd) < 0) { |
478 | 0 | close(fd); |
479 | 0 | clients[fd].state = CLIENT_INVALID; |
480 | 0 | return; |
481 | 0 | } |
482 | | |
483 | 0 | clients[fd].state = CLIENT_HELLO; |
484 | 0 | if (fd > max_client_fd) |
485 | 0 | max_client_fd = fd; |
486 | 0 | } |
487 | | |
488 | | static void terminate_client(int fd) |
489 | 1 | { |
490 | 1 | int port; |
491 | 1 | clients[fd].state = CLIENT_INVALID; |
492 | 1 | close(fd); |
493 | | /* if client requested vchan connection end notify, cancel it */ |
494 | 257 | for (port = 0; port < MAX_CLIENTS; port++) { |
495 | 256 | if (vchan_port_notify_client[port] == fd) |
496 | 256 | vchan_port_notify_client[port] = VCHAN_PORT_UNUSED; |
497 | 256 | } |
498 | 1 | } |
499 | | |
500 | | static void release_vchan_port(int port, int expected_remote_id) |
501 | 44 | { |
502 | | /* release only if was reserved for connection to given domain */ |
503 | 44 | if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) { |
504 | 3 | used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED; |
505 | | /* notify client if requested - it will clear notification request */ |
506 | 3 | if (vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT] != VCHAN_PORT_UNUSED) |
507 | 1 | terminate_client(vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT]); |
508 | 3 | } |
509 | 44 | } |
510 | | |
511 | | static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr) |
512 | 0 | { |
513 | 0 | struct exec_params params; |
514 | 0 | int len = hdr->len-sizeof(params); |
515 | 0 | char buf[len]; |
516 | 0 | int use_default_user = 0; |
517 | 0 | int i; |
518 | |
|
519 | 0 | if (!read_all(fd, ¶ms, sizeof(params))) { |
520 | 0 | terminate_client(fd); |
521 | 0 | return 0; |
522 | 0 | } |
523 | 0 | if (!read_all(fd, buf, len)) { |
524 | 0 | terminate_client(fd); |
525 | 0 | return 0; |
526 | 0 | } |
527 | | |
528 | 0 | if (hdr->type == MSG_SERVICE_CONNECT) { |
529 | | /* if the service was accepted, do not send spurious |
530 | | * MSG_SERVICE_REFUSED when service process itself exit with non-zero |
531 | | * code. Avoid also sending MSG_SERVICE_CONNECT twice. */ |
532 | 0 | for (i = 0; i <= policy_pending_max; i++) { |
533 | 0 | if (policy_pending[i].pid && |
534 | 0 | policy_pending[i].response_sent == RESPONSE_PENDING && |
535 | 0 | strncmp(policy_pending[i].params.ident, buf, len) == 0) { |
536 | 0 | break; |
537 | 0 | } |
538 | 0 | } |
539 | 0 | if (i > policy_pending_max) { |
540 | 0 | LOG(ERROR, "Connection with ident %s not requested or already handled", |
541 | 0 | policy_pending[i].params.ident); |
542 | 0 | terminate_client(fd); |
543 | 0 | return 0; |
544 | 0 | } |
545 | 0 | policy_pending[i].response_sent = RESPONSE_ALLOW; |
546 | 0 | } |
547 | | |
548 | 0 | if (!params.connect_port) { |
549 | 0 | struct exec_params client_params; |
550 | | /* allocate port and send it to the client */ |
551 | 0 | params.connect_port = allocate_vchan_port(params.connect_domain); |
552 | 0 | if (params.connect_port <= 0) { |
553 | 0 | LOG(ERROR, "Failed to allocate new vchan port, too many clients?"); |
554 | 0 | terminate_client(fd); |
555 | 0 | return 0; |
556 | 0 | } |
557 | | /* notify the client when this connection got terminated */ |
558 | 0 | vchan_port_notify_client[params.connect_port-VCHAN_BASE_DATA_PORT] = fd; |
559 | 0 | client_params.connect_port = params.connect_port; |
560 | 0 | client_params.connect_domain = remote_domain_id; |
561 | 0 | hdr->len = sizeof(client_params); |
562 | 0 | if (!write_all(fd, hdr, sizeof(*hdr))) { |
563 | 0 | terminate_client(fd); |
564 | 0 | release_vchan_port(params.connect_port, params.connect_domain); |
565 | 0 | return 0; |
566 | 0 | } |
567 | 0 | if (!write_all(fd, &client_params, sizeof(client_params))) { |
568 | 0 | terminate_client(fd); |
569 | 0 | release_vchan_port(params.connect_port, params.connect_domain); |
570 | 0 | return 0; |
571 | 0 | } |
572 | | /* restore original len value */ |
573 | 0 | hdr->len = len+sizeof(params); |
574 | 0 | } else { |
575 | 0 | assert(params.connect_port >= VCHAN_BASE_DATA_PORT); |
576 | 0 | assert(params.connect_port < VCHAN_BASE_DATA_PORT+MAX_CLIENTS); |
577 | 0 | } |
578 | | |
579 | 0 | if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) { |
580 | 0 | use_default_user = 1; |
581 | 0 | hdr->len -= default_user_keyword_len_without_colon; |
582 | 0 | hdr->len += strlen(default_user); |
583 | 0 | } |
584 | 0 | if (libvchan_send(vchan, hdr, sizeof(*hdr)) != sizeof(*hdr)) |
585 | 0 | handle_vchan_error("send"); |
586 | 0 | if (libvchan_send(vchan, ¶ms, sizeof(params)) != sizeof(params)) |
587 | 0 | handle_vchan_error("send params"); |
588 | 0 | if (use_default_user) { |
589 | 0 | int send_len = strlen(default_user); |
590 | 0 | if (libvchan_send(vchan, default_user, send_len) != send_len) |
591 | 0 | handle_vchan_error("send default_user"); |
592 | 0 | send_len = len-default_user_keyword_len_without_colon; |
593 | 0 | if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon, |
594 | 0 | send_len) != send_len) |
595 | 0 | handle_vchan_error("send buf"); |
596 | 0 | } else |
597 | 0 | if (libvchan_send(vchan, buf, len) < len) |
598 | 0 | handle_vchan_error("send buf"); |
599 | 0 | return 1; |
600 | 0 | } |
601 | | |
602 | | static void handle_cmdline_message_from_client(int fd) |
603 | 0 | { |
604 | 0 | struct msg_header hdr; |
605 | 0 | if (!read_all(fd, &hdr, sizeof hdr)) { |
606 | 0 | terminate_client(fd); |
607 | 0 | return; |
608 | 0 | } |
609 | 0 | switch (hdr.type) { |
610 | 0 | case MSG_EXEC_CMDLINE: |
611 | 0 | case MSG_JUST_EXEC: |
612 | 0 | case MSG_SERVICE_CONNECT: |
613 | 0 | break; |
614 | 0 | default: |
615 | 0 | terminate_client(fd); |
616 | 0 | return; |
617 | 0 | } |
618 | | |
619 | 0 | if (!handle_cmdline_body_from_client(fd, &hdr)) |
620 | | // client disconnected while sending cmdline, above call already |
621 | | // cleaned up client info |
622 | 0 | return; |
623 | 0 | clients[fd].state = CLIENT_RUNNING; |
624 | 0 | } |
625 | | |
626 | | static void handle_client_hello(int fd) |
627 | 0 | { |
628 | 0 | struct msg_header hdr; |
629 | 0 | struct peer_info info; |
630 | |
|
631 | 0 | if (!read_all(fd, &hdr, sizeof hdr)) { |
632 | 0 | terminate_client(fd); |
633 | 0 | return; |
634 | 0 | } |
635 | 0 | if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) { |
636 | 0 | LOG(ERROR, "Invalid HELLO packet received from client %d: " |
637 | 0 | "type %d, len %d", fd, hdr.type, hdr.len); |
638 | 0 | terminate_client(fd); |
639 | 0 | return; |
640 | 0 | } |
641 | 0 | if (!read_all(fd, &info, sizeof info)) { |
642 | 0 | terminate_client(fd); |
643 | 0 | return; |
644 | 0 | } |
645 | 0 | if (info.version != QREXEC_PROTOCOL_VERSION) { |
646 | 0 | LOG(ERROR, "Incompatible client protocol version (remote %d, local %d)", info.version, QREXEC_PROTOCOL_VERSION); |
647 | 0 | terminate_client(fd); |
648 | 0 | return; |
649 | 0 | } |
650 | 0 | clients[fd].state = CLIENT_CMDLINE; |
651 | 0 | } |
652 | | |
653 | | /* handle data received from one of qrexec_client processes */ |
654 | | static void handle_message_from_client(int fd) |
655 | 0 | { |
656 | 0 | char buf[1]; |
657 | |
|
658 | 0 | switch (clients[fd].state) { |
659 | 0 | case CLIENT_HELLO: |
660 | 0 | handle_client_hello(fd); |
661 | 0 | return; |
662 | 0 | case CLIENT_CMDLINE: |
663 | 0 | handle_cmdline_message_from_client(fd); |
664 | 0 | return; |
665 | 0 | case CLIENT_RUNNING: |
666 | | // expected EOF |
667 | 0 | if (read(fd, buf, sizeof(buf)) != 0) { |
668 | 0 | LOG(ERROR, "Unexpected data received from client %d", fd); |
669 | 0 | } |
670 | 0 | terminate_client(fd); |
671 | 0 | return; |
672 | 0 | case CLIENT_INVALID: |
673 | 0 | return; /* nothing to do */ |
674 | 0 | default: |
675 | 0 | LOG(ERROR, "Invalid client state %d", clients[fd].state); |
676 | 0 | exit(1); |
677 | 0 | } |
678 | 0 | } |
679 | | |
680 | | |
681 | | /* |
682 | | * The signal handler executes asynchronously; therefore all it should do is |
683 | | * to set a flag "signal has arrived", and let the main even loop react to this |
684 | | * flag in appropriate moment. |
685 | | */ |
686 | | static void signal_handler(int sig) |
687 | 0 | { |
688 | 0 | switch (sig) { |
689 | 0 | case SIGCHLD: |
690 | 0 | child_exited = 1; |
691 | 0 | break; |
692 | 0 | case SIGTERM: |
693 | 0 | terminate_requested = 1; |
694 | 0 | break; |
695 | 0 | default: |
696 | | /* cannot happen */ |
697 | 0 | abort(); |
698 | 0 | } |
699 | 0 | } |
700 | | |
701 | 328 | static void send_service_refused(libvchan_t *vchan, const struct service_params *untrusted_params) { |
702 | 328 | struct msg_header hdr; |
703 | | |
704 | 328 | hdr.type = MSG_SERVICE_REFUSED; |
705 | 328 | hdr.len = sizeof(*untrusted_params); |
706 | | |
707 | 328 | if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
708 | 0 | LOG(ERROR, "Failed to send MSG_SERVICE_REFUSED hdr to agent"); |
709 | 0 | exit(1); |
710 | 0 | } |
711 | | |
712 | 328 | if (libvchan_send(vchan, untrusted_params, sizeof(*untrusted_params)) != sizeof(*untrusted_params)) { |
713 | 0 | LOG(ERROR, "Failed to send MSG_SERVICE_REFUSED to agent"); |
714 | 0 | exit(1); |
715 | 0 | } |
716 | 328 | } |
717 | | |
718 | | /* clean zombies, check for denied service calls */ |
719 | | static void reap_children(void) |
720 | 0 | { |
721 | 0 | int status; |
722 | 0 | int i; |
723 | |
|
724 | 0 | pid_t pid; |
725 | 0 | while ((pid=waitpid(-1, &status, WNOHANG)) > 0) { |
726 | 0 | for (i = 0; i <= policy_pending_max; i++) { |
727 | 0 | if (policy_pending[i].pid == pid) { |
728 | 0 | if (!WIFEXITED(status)) |
729 | 0 | continue; |
730 | 0 | status = WEXITSTATUS(status); |
731 | 0 | if (status != 0) { |
732 | 0 | if (policy_pending[i].response_sent != RESPONSE_PENDING) { |
733 | 0 | LOG(ERROR, "qrexec-policy-exec for connection %s exited with code %d, but the response (%s) was already sent", |
734 | 0 | policy_pending[i].params.ident, status, |
735 | 0 | policy_pending[i].response_sent == RESPONSE_ALLOW ? "allow" : "deny"); |
736 | 0 | } else { |
737 | 0 | policy_pending[i].response_sent = RESPONSE_DENY; |
738 | 0 | send_service_refused(vchan, &policy_pending[i].params); |
739 | 0 | } |
740 | 0 | } else { |
741 | 0 | policy_pending[i].response_sent = RESPONSE_ALLOW; |
742 | 0 | } |
743 | | /* in case of allowed calls, we will do the rest in |
744 | | * MSG_SERVICE_CONNECT from client handler */ |
745 | 0 | policy_pending[i].pid = 0; |
746 | 0 | while (policy_pending_max > 0 && |
747 | 0 | policy_pending[policy_pending_max].pid == 0) |
748 | 0 | policy_pending_max--; |
749 | 0 | break; |
750 | 0 | } |
751 | 0 | } |
752 | 0 | } |
753 | 0 | child_exited = 0; |
754 | 0 | } |
755 | | |
756 | 114 | static int find_policy_pending_slot(void) { |
757 | 6.55k | for (int i = 0; i < MAX_CLIENTS; i++) { |
758 | 6.55k | if (policy_pending[i].pid == 0) { |
759 | 114 | if (i > policy_pending_max) |
760 | 114 | policy_pending_max = i; |
761 | 114 | return i; |
762 | 114 | } |
763 | 6.55k | } |
764 | 0 | return -1; |
765 | 114 | } |
766 | | |
767 | | static void sanitize_name(char * untrusted_s_signed, char *extra_allowed_chars) |
768 | 605 | { |
769 | 605 | unsigned char * untrusted_s; |
770 | 10.6k | for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) { |
771 | 10.0k | if (*untrusted_s >= 'a' && *untrusted_s <= 'z') |
772 | 1.90k | continue; |
773 | 8.13k | if (*untrusted_s >= 'A' && *untrusted_s <= 'Z') |
774 | 1.53k | continue; |
775 | 6.60k | if (*untrusted_s >= '0' && *untrusted_s <= '9') |
776 | 1.21k | continue; |
777 | 5.38k | if (*untrusted_s == '_' || |
778 | 5.38k | *untrusted_s == '-' || |
779 | 5.38k | *untrusted_s == '.') |
780 | 1.87k | continue; |
781 | 3.50k | if (extra_allowed_chars && strchr(extra_allowed_chars, *untrusted_s)) |
782 | 511 | continue; |
783 | 2.99k | *untrusted_s = '_'; |
784 | 2.99k | } |
785 | 605 | } |
786 | | |
787 | | static int parse_policy_response( |
788 | | char *response, |
789 | | size_t result_bytes, |
790 | | bool daemon, |
791 | | char **user, |
792 | | char **target, |
793 | | char **requested_target, |
794 | | int *autostart |
795 | 0 | ) { |
796 | 0 | *user = *target = *requested_target = NULL; |
797 | 0 | int result = *autostart = -1; |
798 | 0 | const char *const msg = daemon ? "qrexec-policy-daemon" : "qrexec-policy-exec"; |
799 | | // At least one byte must be returned |
800 | 0 | if (result_bytes < 1) { |
801 | 0 | LOG(ERROR, "%s didn't return any data", msg); |
802 | 0 | return RESPONSE_MALFORMED; |
803 | 0 | } |
804 | | // Forbid NUL bytes in response. qrexec_read_all_to_malloc() has added the |
805 | | // NUL terminator already. |
806 | 0 | if (strlen(response) != result_bytes) { |
807 | 0 | LOG(ERROR, "%s wrote a NUL byte", msg); |
808 | 0 | return RESPONSE_MALFORMED; |
809 | 0 | } |
810 | | // Strip any trailing newlines. |
811 | 0 | if (response[result_bytes - 1] == '\n') { |
812 | 0 | result_bytes--; |
813 | 0 | response[result_bytes] = '\0'; |
814 | 0 | } |
815 | 0 | char *resp = response, *current_response; |
816 | 0 | while ((current_response = strsep(&resp, "\n"))) { |
817 | 0 | if (!strncmp(current_response, "result=", sizeof("result=") - 1)) { |
818 | 0 | current_response += sizeof("result=") - 1; |
819 | 0 | if (result != -1) { |
820 | 0 | goto bad_response; |
821 | 0 | } |
822 | 0 | if (!strcmp(current_response, "allow")) |
823 | 0 | result = 0; |
824 | 0 | else if (!strcmp(current_response, "deny")) { |
825 | 0 | result = 1; |
826 | 0 | } else { |
827 | 0 | goto bad_response; |
828 | 0 | } |
829 | 0 | } else if (!strncmp(current_response, "user=", sizeof("user=") - 1)) { |
830 | 0 | if (*user) |
831 | 0 | goto bad_response; |
832 | 0 | *user = strdup(current_response + (sizeof("user=") - 1)); |
833 | 0 | if (*user == NULL) |
834 | 0 | abort(); |
835 | 0 | } else if (!strncmp(current_response, "target=", sizeof("target=") - 1)) { |
836 | 0 | if (*target != NULL) |
837 | 0 | goto bad_response; |
838 | 0 | *target = strdup(current_response + (sizeof("target=") - 1)); |
839 | 0 | if (*target == NULL) |
840 | 0 | abort(); |
841 | 0 | } else if (!strncmp(current_response, "autostart=", sizeof("autostart=") - 1)) { |
842 | 0 | current_response += sizeof("autostart=") - 1; |
843 | 0 | if (*autostart != -1) |
844 | 0 | goto bad_response; |
845 | 0 | if (!strcmp(current_response, "True")) |
846 | 0 | *autostart = 1; |
847 | 0 | else if (!strcmp(current_response, "False")) |
848 | 0 | *autostart = 0; |
849 | 0 | else |
850 | 0 | goto bad_response; |
851 | 0 | } else if (!strncmp(current_response, "requested_target=", sizeof("requested_target=") - 1)) { |
852 | 0 | if (*requested_target != NULL) |
853 | 0 | goto bad_response; |
854 | 0 | *requested_target = strdup(current_response + (sizeof("requested_target=") - 1)); |
855 | 0 | if (*requested_target == NULL) |
856 | 0 | abort(); |
857 | 0 | } else { |
858 | 0 | char *p = strchr(current_response, '='); |
859 | 0 | if (p == NULL) |
860 | 0 | goto bad_response; |
861 | 0 | *p = '\0'; |
862 | 0 | LOG(ERROR, "Unknown response key %s, ignoring", current_response); |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | 0 | switch (result) { |
867 | 0 | case 0: |
868 | 0 | if (*user == NULL || *target == NULL || *requested_target == NULL || *autostart == -1) |
869 | 0 | break; |
870 | 0 | return RESPONSE_ALLOW; |
871 | 0 | case 1: |
872 | 0 | if (*user != NULL || *target != NULL || *requested_target != NULL || *autostart != -1) |
873 | 0 | break; |
874 | 0 | return RESPONSE_DENY; |
875 | 0 | default: |
876 | 0 | break; |
877 | 0 | } |
878 | | |
879 | 0 | bad_response: |
880 | 0 | LOG(ERROR, "%s sent invalid response", msg); |
881 | 0 | return RESPONSE_MALFORMED; |
882 | 0 | } |
883 | | |
884 | | struct QrexecPolicyRequest { |
885 | | }; |
886 | | |
887 | | static void send_request_to_daemon( |
888 | | const int daemon_socket, |
889 | | const char *remote_domain_name, |
890 | | const char *target_domain, |
891 | | const char *service_name) |
892 | 0 | { |
893 | 0 | char *command; |
894 | 0 | ssize_t bytes_sent = 0; |
895 | 0 | int command_size = asprintf(&command, |
896 | 0 | "source=%s\n" |
897 | 0 | "intended_target=%s\n" |
898 | 0 | "service_and_arg=%s\n\n", |
899 | 0 | remote_domain_name, |
900 | 0 | target_domain, |
901 | 0 | service_name); |
902 | 0 | if (command_size < 0) { |
903 | 0 | PERROR("failed to construct request"); |
904 | 0 | _exit(126); |
905 | 0 | } |
906 | | |
907 | 0 | for (int i = 0; i < command_size; i += bytes_sent) { |
908 | 0 | bytes_sent = send(daemon_socket, command + i, command_size - i, MSG_NOSIGNAL); |
909 | 0 | if (bytes_sent > command_size - i) |
910 | 0 | abort(); // kernel read beyond buffer bounds? |
911 | 0 | if (bytes_sent < 0) { |
912 | 0 | assert(bytes_sent == -1); |
913 | 0 | PERROR("send to socket failed"); |
914 | 0 | _exit(126); |
915 | 0 | } |
916 | 0 | } |
917 | 0 | free(command); |
918 | 0 | } |
919 | | |
920 | | /* |
921 | | * Called when agent sends a message asking to execute a predefined command. |
922 | | */ |
923 | | |
924 | | static enum policy_response connect_daemon_socket( |
925 | | const char *remote_domain_name, |
926 | | const char *target_domain, |
927 | | const char *service_name, |
928 | | char **user, |
929 | | char **target, |
930 | | char **requested_target, |
931 | | int *autostart |
932 | 0 | ) { |
933 | 0 | int pid = -1; |
934 | 0 | struct sockaddr_un daemon_socket_address = { |
935 | 0 | .sun_family = AF_UNIX, |
936 | 0 | .sun_path = QREXEC_SOCKET_PATH |
937 | 0 | }; |
938 | |
|
939 | 0 | int daemon_socket = socket(AF_UNIX, SOCK_STREAM, 0); |
940 | 0 | if (daemon_socket < 0) { |
941 | 0 | PERROR("socket creation failed"); |
942 | 0 | _exit(126); |
943 | 0 | } |
944 | | |
945 | 0 | int connect_result = connect(daemon_socket, |
946 | 0 | (struct sockaddr *) &daemon_socket_address, |
947 | 0 | sizeof(daemon_socket_address)); |
948 | 0 | if (connect_result == 0) { |
949 | 0 | send_request_to_daemon(daemon_socket, |
950 | 0 | remote_domain_name, |
951 | 0 | target_domain, |
952 | 0 | service_name); |
953 | 0 | size_t result_bytes; |
954 | | // this closes the socket |
955 | 0 | char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes); |
956 | 0 | int policy_result = parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart); |
957 | 0 | if (policy_result != RESPONSE_MALFORMED) { |
958 | | // This leaks 'result', but as the code execs later anyway this isn't a problem. |
959 | | // 'result' cannot be freed as 'user', 'target', and 'requested_target' point into |
960 | | // the same buffer. |
961 | 0 | return policy_result; |
962 | 0 | } |
963 | 0 | free(result); |
964 | 0 | } else { |
965 | 0 | PERROR("connection to socket failed"); |
966 | 0 | assert(connect_result == -1); |
967 | 0 | if (close(daemon_socket)) |
968 | 0 | abort(); |
969 | 0 | } |
970 | 0 | int fds[2]; |
971 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds)) { |
972 | 0 | PERROR("socketpair()"); |
973 | 0 | _exit(126); |
974 | 0 | } |
975 | 0 | daemon_socket = fds[0]; |
976 | |
|
977 | 0 | pid = fork(); |
978 | 0 | switch (pid) { |
979 | 0 | case -1: |
980 | 0 | LOG(ERROR, "Could not fork!"); |
981 | 0 | _exit(126); |
982 | 0 | case 0: |
983 | 0 | if (close(fds[0])) |
984 | 0 | abort(); |
985 | 0 | if (dup2(fds[1], 0) != 0 || dup2(fds[1], 1) != 1) { |
986 | 0 | PERROR("dup2()"); |
987 | 0 | _exit(126); |
988 | 0 | } |
989 | 0 | if (close(fds[1])) |
990 | 0 | abort(); |
991 | 0 | char remote_domain_id_str[10]; |
992 | 0 | int v = snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d", |
993 | 0 | remote_domain_id); |
994 | 0 | if (v >= 1 && v < (int)sizeof(remote_domain_id_str)) { |
995 | 0 | execl(policy_program, |
996 | 0 | "qrexec-policy-exec", |
997 | 0 | "--", |
998 | 0 | remote_domain_name, |
999 | 0 | target_domain, |
1000 | 0 | service_name, |
1001 | 0 | NULL); |
1002 | 0 | PERROR("execl"); |
1003 | 0 | } else { |
1004 | 0 | PERROR("snprintf"); |
1005 | 0 | } |
1006 | 0 | _exit(126); |
1007 | 0 | default: |
1008 | 0 | if (close(fds[1])) |
1009 | 0 | abort(); |
1010 | 0 | size_t result_bytes; |
1011 | 0 | int status; |
1012 | | // this closes the socket |
1013 | 0 | char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes); |
1014 | 0 | do { |
1015 | 0 | if (waitpid(pid, &status, 0) != pid) { |
1016 | 0 | PERROR("waitpid"); |
1017 | 0 | _exit(126); |
1018 | 0 | } |
1019 | 0 | } while (!WIFEXITED(status)); |
1020 | 0 | if (WEXITSTATUS(status) != 0) { |
1021 | 0 | LOG(ERROR, "qrexec-policy-exec failed"); |
1022 | 0 | _exit(126); |
1023 | 0 | } |
1024 | | // This leaks 'result', but as the code execs later anyway this isn't a problem. |
1025 | | // 'result' cannot be freed as 'user', 'target', and 'requested_target' point into |
1026 | | // the same buffer. |
1027 | 0 | return parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart); |
1028 | 0 | } |
1029 | 0 | } |
1030 | | |
1031 | | static void handle_execute_service( |
1032 | | const int remote_domain_id, |
1033 | | const char *remote_domain_name, |
1034 | | const char *target_domain, |
1035 | | const char *service_name, |
1036 | | const struct service_params *request_id) |
1037 | 114 | { |
1038 | 114 | int i; |
1039 | 114 | int policy_pending_slot; |
1040 | 114 | pid_t pid; |
1041 | | |
1042 | 114 | policy_pending_slot = find_policy_pending_slot(); |
1043 | 114 | if (policy_pending_slot < 0) { |
1044 | 0 | LOG(ERROR, "Service request denied, too many pending requests"); |
1045 | 0 | send_service_refused(vchan, request_id); |
1046 | 0 | return; |
1047 | 0 | } |
1048 | | |
1049 | 114 | switch (pid=fork()) { |
1050 | 0 | case -1: |
1051 | 0 | PERROR("fork"); |
1052 | 0 | exit(1); |
1053 | 0 | case 0: |
1054 | 0 | break; |
1055 | 114 | default: |
1056 | 114 | policy_pending[policy_pending_slot].pid = pid; |
1057 | 114 | policy_pending[policy_pending_slot].params = *request_id; |
1058 | 114 | policy_pending[policy_pending_slot].response_sent = RESPONSE_PENDING; |
1059 | 114 | return; |
1060 | 114 | } |
1061 | | |
1062 | 0 | for (i = 3; i < MAX_FDS; i++) |
1063 | 0 | close(i); |
1064 | |
|
1065 | 0 | char *user, *target, *requested_target; |
1066 | 0 | int autostart; |
1067 | 0 | int policy_response = |
1068 | 0 | connect_daemon_socket(remote_domain_name, target_domain, service_name, |
1069 | 0 | &user, &target, &requested_target, &autostart); |
1070 | |
|
1071 | 0 | if (policy_response != RESPONSE_ALLOW) |
1072 | 0 | _exit(126); |
1073 | | |
1074 | | /* Replace the target domain with the version normalized by the policy engine */ |
1075 | 0 | target_domain = requested_target; |
1076 | 0 | char *cmd = NULL; |
1077 | 0 | bool disposable = false; |
1078 | 0 | size_t resp_len; |
1079 | | |
1080 | | /* |
1081 | | * If there was no service argument, pretend that an empty argument was |
1082 | | * provided by appending "+" to the service name. |
1083 | | */ |
1084 | 0 | const char *const trailer = strchr(service_name, '+') ? "" : "+"; |
1085 | | |
1086 | | /* Check if the target is dom0, which requires special handling. */ |
1087 | 0 | bool target_is_dom0 = strcmp(target, "@adminvm") == 0 || |
1088 | 0 | strcmp(target, "dom0") == 0; |
1089 | 0 | if (target_is_dom0) { |
1090 | 0 | char *type; |
1091 | 0 | bool target_is_keyword = target_domain[0] == '@'; |
1092 | 0 | if (target_is_keyword) { |
1093 | 0 | target_domain++; |
1094 | 0 | type = "keyword"; |
1095 | 0 | } else { |
1096 | 0 | type = "name"; |
1097 | 0 | } |
1098 | 0 | if (asprintf(&cmd, "QUBESRPC %s%s %s %s %s", |
1099 | 0 | service_name, |
1100 | 0 | trailer, |
1101 | 0 | remote_domain_name, |
1102 | 0 | type, |
1103 | 0 | target_domain) <= 0) |
1104 | 0 | _exit(126); |
1105 | 0 | } else { |
1106 | 0 | char *buf; |
1107 | 0 | if (strncmp("@dispvm:", target, sizeof("@dispvm:") - 1) == 0) { |
1108 | 0 | disposable = true; |
1109 | 0 | buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len); |
1110 | 0 | if (!buf) // error already printed by qubesd_call |
1111 | 0 | _exit(126); |
1112 | 0 | if (memcmp(buf, "0", 2) == 0) { |
1113 | | /* we exec later so memory leaks do not matter */ |
1114 | 0 | target = buf + 2; |
1115 | 0 | } else { |
1116 | 0 | if (memcmp(buf, "2", 2) == 0) { |
1117 | 0 | LOG(ERROR, "qubesd could not create disposable VM: %s", buf + 2); |
1118 | 0 | } else { |
1119 | 0 | LOG(ERROR, "invalid response to admin.vm.CreateDisposable"); |
1120 | 0 | } |
1121 | 0 | _exit(126); |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | if (asprintf(&cmd, "%s:QUBESRPC %s%s %s", |
1125 | 0 | user, |
1126 | 0 | service_name, |
1127 | 0 | trailer, |
1128 | 0 | remote_domain_name) <= 0) |
1129 | 0 | _exit(126); |
1130 | 0 | if (autostart) { |
1131 | 0 | buf = qubesd_call(target, "admin.vm.Start", "", &resp_len); |
1132 | 0 | if (!buf) // error already printed by qubesd_call |
1133 | 0 | _exit(126); |
1134 | 0 | if (!((memcmp(buf, "0", 2) == 0) || |
1135 | 0 | (resp_len >= 24 && memcmp(buf, "2\0QubesVMNotHaltedError", 24) == 0))) { |
1136 | 0 | if (memcmp(buf, "2", 2) == 0) { |
1137 | 0 | LOG(ERROR, "qubesd could not start VM %s: %s", target, buf + 2); |
1138 | 0 | } else { |
1139 | 0 | LOG(ERROR, "invalid response to admin.vm.Start"); |
1140 | 0 | } |
1141 | 0 | _exit(126); |
1142 | 0 | } |
1143 | 0 | free(buf); |
1144 | 0 | } |
1145 | 0 | } |
1146 | 0 | char *cid; |
1147 | 0 | if (asprintf(&cid, "%s,%s,%d", request_id->ident, remote_domain_name, remote_domain_id) <= 0) |
1148 | 0 | _exit(126); |
1149 | | |
1150 | 0 | const char *to_exec[] = { |
1151 | 0 | "/usr/bin/qrexec-client", |
1152 | 0 | disposable ? "-EWkd" : "-Ed", |
1153 | 0 | target, |
1154 | 0 | "-c", |
1155 | 0 | cid, |
1156 | 0 | "--", |
1157 | 0 | cmd, |
1158 | 0 | NULL, |
1159 | 0 | }; |
1160 | 0 | execv(to_exec[0], (char **)to_exec); |
1161 | 0 | LOG(ERROR, "execve() failed: %m"); |
1162 | 0 | _exit(126); |
1163 | 0 | } |
1164 | | |
1165 | | |
1166 | | static void handle_connection_terminated(void) |
1167 | 97 | { |
1168 | 97 | struct exec_params untrusted_params, params; |
1169 | | |
1170 | 97 | if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) |
1171 | 97 | != sizeof(untrusted_params)) |
1172 | 4 | handle_vchan_error("recv params"); |
1173 | | /* sanitize start */ |
1174 | 97 | if (untrusted_params.connect_port < VCHAN_BASE_DATA_PORT || |
1175 | 97 | untrusted_params.connect_port >= VCHAN_BASE_DATA_PORT+MAX_CLIENTS) { |
1176 | 49 | LOG(ERROR, "Invalid port in MSG_CONNECTION_TERMINATED (%d)", |
1177 | 49 | untrusted_params.connect_port); |
1178 | 49 | exit(1); |
1179 | 49 | } |
1180 | | /* untrusted_params.connect_domain even if invalid will not harm - in worst |
1181 | | * case the port will not be released */ |
1182 | 97 | params = untrusted_params; |
1183 | | /* sanitize end */ |
1184 | 97 | release_vchan_port(params.connect_port, params.connect_domain); |
1185 | 97 | } |
1186 | | |
1187 | | static void sanitize_message_from_agent(struct msg_header *untrusted_header) |
1188 | 825 | { |
1189 | 825 | switch (untrusted_header->type) { |
1190 | 116 | case MSG_TRIGGER_SERVICE: |
1191 | 116 | if (protocol_version >= QREXEC_PROTOCOL_V3) { |
1192 | 1 | LOG(ERROR, "agent sent (old) MSG_TRIGGER_SERVICE " |
1193 | 1 | "although it uses protocol %d", protocol_version); |
1194 | 1 | exit(1); |
1195 | 1 | } |
1196 | 116 | if (untrusted_header->len != sizeof(struct trigger_service_params)) { |
1197 | 40 | LOG(ERROR, "agent sent invalid MSG_TRIGGER_SERVICE packet"); |
1198 | 40 | exit(1); |
1199 | 40 | } |
1200 | 116 | break; |
1201 | 500 | case MSG_TRIGGER_SERVICE3: |
1202 | 500 | if (protocol_version < QREXEC_PROTOCOL_V3) { |
1203 | 1 | LOG(ERROR, "agent sent (new) MSG_TRIGGER_SERVICE3 " |
1204 | 1 | "although it uses protocol %d", protocol_version); |
1205 | 1 | exit(1); |
1206 | 1 | } |
1207 | 500 | if (untrusted_header->len <= sizeof(struct trigger_service_params3)) { |
1208 | 6 | LOG(ERROR, "agent sent invalid MSG_TRIGGER_SERVICE3 packet"); |
1209 | 6 | exit(1); |
1210 | 6 | } |
1211 | 500 | if (untrusted_header->len - sizeof(struct trigger_service_params3) |
1212 | 500 | > MAX_SERVICE_NAME_LEN) { |
1213 | 48 | LOG(ERROR, "agent sent too large MSG_TRIGGER_SERVICE3 packet"); |
1214 | 48 | exit(1); |
1215 | 48 | } |
1216 | 500 | break; |
1217 | 139 | case MSG_CONNECTION_TERMINATED: |
1218 | 139 | if (untrusted_header->len != sizeof(struct exec_params)) { |
1219 | 42 | LOG(ERROR, "agent sent invalid MSG_CONNECTION_TERMINATED packet"); |
1220 | 42 | exit(1); |
1221 | 42 | } |
1222 | 139 | break; |
1223 | 70 | default: |
1224 | 70 | LOG(ERROR, "unknown mesage type 0x%x from agent", |
1225 | 70 | untrusted_header->type); |
1226 | 70 | exit(1); |
1227 | 825 | } |
1228 | 825 | } |
1229 | | |
1230 | | static bool validate_request_id(struct service_params *untrusted_params, const char *msg) |
1231 | 442 | { |
1232 | 9.60k | for (size_t i = 0; i < sizeof(untrusted_params->ident); ++i) { |
1233 | 9.49k | switch (untrusted_params->ident[i]) { |
1234 | 1.42k | case '0' ... '9': |
1235 | 4.73k | case 'A' ... 'Z': |
1236 | 8.39k | case 'a' ... 'z': |
1237 | 8.58k | case '_': |
1238 | 8.77k | case '-': |
1239 | 9.07k | case '.': |
1240 | 9.16k | case ' ': |
1241 | 9.16k | continue; |
1242 | 215 | case '\0': { |
1243 | 215 | size_t terminator_offset = i; |
1244 | | /* Ensure that nothing non-NUL follows the terminator */ |
1245 | 1.97k | for (i++; i < sizeof(untrusted_params->ident); i++) { |
1246 | 1.83k | if (untrusted_params->ident[i]) { |
1247 | 67 | LOG(ERROR, "Non-NUL byte %u at offset %zu follows NUL terminator at offset %zu in message %s", |
1248 | 67 | untrusted_params->ident[i], i, terminator_offset, msg); |
1249 | 67 | return false; |
1250 | 67 | } |
1251 | 1.83k | } |
1252 | 148 | return true; |
1253 | 215 | } |
1254 | 119 | default: |
1255 | 119 | LOG(ERROR, "Bad byte %u at offset %zu for message %s", untrusted_params->ident[i], i, msg); |
1256 | 119 | return false; |
1257 | 9.49k | } |
1258 | 9.49k | } |
1259 | 108 | LOG(ERROR, "No NUL terminator in message %s", msg); |
1260 | 108 | return false; // no NUL terminator |
1261 | 442 | } |
1262 | | |
1263 | 595 | #define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0 |
1264 | | |
1265 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
1266 | | static |
1267 | | #endif |
1268 | | void handle_message_from_agent(void) |
1269 | 832 | { |
1270 | 832 | struct msg_header hdr, untrusted_hdr; |
1271 | | |
1272 | 832 | if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) |
1273 | 832 | != sizeof(untrusted_hdr)) |
1274 | 7 | handle_vchan_error("recv hdr"); |
1275 | | /* sanitize start */ |
1276 | 832 | sanitize_message_from_agent(&untrusted_hdr); |
1277 | 832 | hdr = untrusted_hdr; |
1278 | | /* sanitize end */ |
1279 | | |
1280 | | // fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id, |
1281 | | // hdr.len); |
1282 | | |
1283 | 832 | switch (hdr.type) { |
1284 | 75 | case MSG_TRIGGER_SERVICE: { |
1285 | 75 | struct trigger_service_params untrusted_params, params; |
1286 | 75 | if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) |
1287 | 75 | != sizeof(untrusted_params)) |
1288 | 13 | handle_vchan_error("recv params"); |
1289 | | |
1290 | | /* sanitize start */ |
1291 | 75 | ENSURE_NULL_TERMINATED(untrusted_params.service_name); |
1292 | 75 | ENSURE_NULL_TERMINATED(untrusted_params.target_domain); |
1293 | 75 | sanitize_name(untrusted_params.service_name, "+"); |
1294 | 75 | sanitize_name(untrusted_params.target_domain, "@:"); |
1295 | 75 | if (!validate_request_id(&untrusted_params.request_id, "MSG_TRIGGER_SERVICE")) { |
1296 | 49 | send_service_refused(vchan, &untrusted_params.request_id); |
1297 | 49 | return; |
1298 | 49 | } |
1299 | 26 | params = untrusted_params; |
1300 | | /* sanitize end */ |
1301 | | |
1302 | 26 | handle_execute_service(remote_domain_id, remote_domain_name, |
1303 | 26 | params.target_domain, |
1304 | 26 | params.service_name, |
1305 | 26 | ¶ms.request_id); |
1306 | 26 | return; |
1307 | 75 | } |
1308 | 445 | case MSG_TRIGGER_SERVICE3: { |
1309 | 445 | struct trigger_service_params3 untrusted_params3, params3; |
1310 | 445 | size_t service_name_len = hdr.len - sizeof(untrusted_params3), nul_offset; |
1311 | 445 | char *untrusted_service_name = malloc(service_name_len), *service_name = NULL; |
1312 | | |
1313 | 445 | if (!untrusted_service_name) |
1314 | 0 | handle_vchan_error("malloc(service_name)"); |
1315 | | |
1316 | 445 | if (libvchan_recv(vchan, &untrusted_params3, sizeof(untrusted_params3)) |
1317 | 445 | != sizeof(untrusted_params3)) { |
1318 | 29 | free(untrusted_service_name); |
1319 | 29 | handle_vchan_error("recv params3"); |
1320 | 29 | } |
1321 | 445 | if (libvchan_recv(vchan, untrusted_service_name, service_name_len) |
1322 | 445 | != (int)service_name_len) { |
1323 | 36 | free(untrusted_service_name); |
1324 | 36 | handle_vchan_error("recv params3(service_name)"); |
1325 | 36 | } |
1326 | 445 | service_name_len -= 1; |
1327 | | |
1328 | | /* sanitize start */ |
1329 | 445 | ENSURE_NULL_TERMINATED(untrusted_params3.target_domain); |
1330 | 445 | sanitize_name(untrusted_params3.target_domain, "@:"); |
1331 | 445 | if (!validate_request_id(&untrusted_params3.request_id, "MSG_TRIGGER_SERVICE3")) |
1332 | 245 | goto fail; |
1333 | 200 | params3 = untrusted_params3; |
1334 | 200 | if (untrusted_service_name[service_name_len] != 0) { |
1335 | 8 | LOG(ERROR, "Service name not NUL-terminated"); |
1336 | 8 | goto fail; |
1337 | 8 | } |
1338 | 192 | nul_offset = strlen(untrusted_service_name); |
1339 | 192 | if (nul_offset != service_name_len) { |
1340 | 23 | LOG(ERROR, "Service name contains NUL byte at offset %zu", nul_offset); |
1341 | 23 | goto fail; |
1342 | 23 | } |
1343 | 169 | switch (untrusted_service_name[0]) { |
1344 | 2 | case '\0': |
1345 | 2 | LOG(ERROR, "Empty service name not allowed"); |
1346 | 2 | goto fail; |
1347 | 1 | case '+': |
1348 | 1 | LOG(ERROR, "Service name must not start with '+'"); |
1349 | 1 | goto fail; |
1350 | 101 | default: |
1351 | 101 | sanitize_name(untrusted_service_name, "+"); |
1352 | 101 | service_name = untrusted_service_name; |
1353 | 101 | untrusted_service_name = NULL; |
1354 | | /* sanitize end */ |
1355 | | |
1356 | 101 | handle_execute_service(remote_domain_id, remote_domain_name, |
1357 | 101 | params3.target_domain, |
1358 | 101 | service_name, |
1359 | 101 | ¶ms3.request_id); |
1360 | 101 | free(service_name); |
1361 | 101 | return; |
1362 | 169 | } |
1363 | 279 | fail: |
1364 | 279 | send_service_refused(vchan, &untrusted_params3.request_id); |
1365 | 279 | free(untrusted_service_name); |
1366 | 279 | return; |
1367 | 169 | } |
1368 | 97 | case MSG_CONNECTION_TERMINATED: |
1369 | 97 | handle_connection_terminated(); |
1370 | 97 | return; |
1371 | 832 | } |
1372 | 832 | } |
1373 | | |
1374 | | /* qrexec-agent has disconnected, cleanup local state and try to connect again. |
1375 | | * If remote domain dies, terminate qrexec-daemon. |
1376 | | */ |
1377 | 0 | static int handle_agent_restart(int xid) { |
1378 | 0 | size_t i; |
1379 | | |
1380 | | // Stop listening. |
1381 | 0 | unlink_qrexec_socket(); |
1382 | 0 | close(qrexec_daemon_unix_socket_fd); |
1383 | | |
1384 | | /* Close old (dead) vchan connection. */ |
1385 | 0 | libvchan_close(vchan); |
1386 | 0 | vchan = NULL; |
1387 | | |
1388 | | /* Disconnect all local clients. This will look like all the qrexec |
1389 | | * connections were terminated, which isn't necessary true (established |
1390 | | * qrexec connection may survive qrexec-agent and qrexec-daemon restart), |
1391 | | * but we won't be notified about its termination. This may kill DispVM |
1392 | | * prematurely (if anyone restarts qrexec-agent inside DispVM), but it's |
1393 | | * better than the alternative (leaking DispVMs). |
1394 | | * |
1395 | | * But, do not mark related vchan ports as unused. Since we won't get call |
1396 | | * end notification, we don't know when such ports will really be unused. |
1397 | | */ |
1398 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
1399 | 0 | if (clients[i].state != CLIENT_INVALID) |
1400 | 0 | terminate_client(i); |
1401 | 0 | } |
1402 | | |
1403 | | /* Abort pending qrexec requests */ |
1404 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
1405 | 0 | if (policy_pending[i].pid != 0) |
1406 | 0 | policy_pending[i].pid = 0; |
1407 | 0 | } |
1408 | 0 | policy_pending_max = -1; |
1409 | | |
1410 | | /* Restore default SIGTERM handling: libvchan_client_init() might block |
1411 | | * indefinitely, so we want the program to be killable. |
1412 | | */ |
1413 | 0 | if (signal(SIGTERM, SIG_DFL) == SIG_ERR) |
1414 | 0 | err(1, "signal()"); |
1415 | 0 | if (terminate_requested) |
1416 | 0 | return -1; |
1417 | | |
1418 | | #ifdef COVERAGE |
1419 | | /* Dump coverage in case we are killed here. */ |
1420 | | __gcov_dump(); |
1421 | | __gcov_reset(); |
1422 | | #endif |
1423 | | |
1424 | 0 | vchan = libvchan_client_init(remote_domain_id, VCHAN_BASE_PORT); |
1425 | 0 | if (!vchan) { |
1426 | 0 | PERROR("cannot connect to qrexec agent"); |
1427 | 0 | return -1; |
1428 | 0 | } |
1429 | 0 | if (handle_agent_hello(vchan, remote_domain_name) < 0) { |
1430 | 0 | libvchan_close(vchan); |
1431 | 0 | vchan = NULL; |
1432 | 0 | return -1; |
1433 | 0 | } |
1434 | 0 | LOG(INFO, "qrexec-agent has reconnected"); |
1435 | |
|
1436 | 0 | struct sigaction action = { |
1437 | 0 | .sa_handler = signal_handler, |
1438 | 0 | .sa_flags = 0, |
1439 | 0 | }; |
1440 | 0 | if (sigaction(SIGTERM, &action, NULL)) |
1441 | 0 | err(1, "sigaction"); |
1442 | | |
1443 | 0 | qrexec_daemon_unix_socket_fd = |
1444 | 0 | create_qrexec_socket(xid, remote_domain_name); |
1445 | 0 | return 0; |
1446 | 0 | } |
1447 | | |
1448 | | static struct option longopts[] = { |
1449 | | { "help", no_argument, 0, 'h' }, |
1450 | | { "quiet", no_argument, 0, 'q' }, |
1451 | | { "socket-dir", required_argument, 0, 'd' + 128 }, |
1452 | | { "policy-program", required_argument, 0, 'p' }, |
1453 | | { "direct", no_argument, 0, 'D' }, |
1454 | | { NULL, 0, 0, 0 }, |
1455 | | }; |
1456 | | |
1457 | | static _Noreturn void usage(const char *argv0) |
1458 | 0 | { |
1459 | 0 | fprintf(stderr, "usage: %s [options] domainid domain-name [default user]\n", argv0); |
1460 | 0 | fprintf(stderr, "Options:\n"); |
1461 | 0 | fprintf(stderr, " -h, --help - display usage\n"); |
1462 | 0 | fprintf(stderr, " -q, --quiet - quiet mode\n"); |
1463 | 0 | fprintf(stderr, " --socket-dir=PATH - directory for qrexec socket, default: %s\n", |
1464 | 0 | QREXEC_DAEMON_SOCKET_DIR); |
1465 | 0 | fprintf(stderr, " -p, --policy-program=PATH - program to execute to check policy, default: %s\n", |
1466 | 0 | QREXEC_POLICY_PROGRAM); |
1467 | 0 | fprintf(stderr, " -D, --direct - run directly, don't daemonize, log to stderr\n"); |
1468 | 0 | exit(1); |
1469 | 0 | } |
1470 | | |
1471 | | int main(int argc, char **argv) |
1472 | 0 | { |
1473 | 0 | int i, opt; |
1474 | 0 | sigset_t selectmask; |
1475 | |
|
1476 | 0 | setup_logging("qrexec-daemon"); |
1477 | |
|
1478 | 0 | while ((opt=getopt_long(argc, argv, "hqp:D", longopts, NULL)) != -1) { |
1479 | 0 | switch (opt) { |
1480 | 0 | case 'q': |
1481 | 0 | opt_quiet = 1; |
1482 | 0 | break; |
1483 | 0 | case 'd' + 128: |
1484 | 0 | if ((socket_dir = strdup(optarg)) == NULL) |
1485 | 0 | err(1, "strdup()"); |
1486 | 0 | break; |
1487 | 0 | case 'p': |
1488 | 0 | if ((policy_program = strdup(optarg)) == NULL) |
1489 | 0 | err(1, "strdup()"); |
1490 | 0 | break; |
1491 | 0 | case 'D': |
1492 | 0 | opt_direct = 1; |
1493 | 0 | break; |
1494 | 0 | case 'h': |
1495 | 0 | default: /* '?' */ |
1496 | 0 | usage(argv[0]); |
1497 | 0 | } |
1498 | 0 | } |
1499 | 0 | if (argc - optind < 2 || argc - optind > 3) { |
1500 | 0 | usage(argv[0]); |
1501 | 0 | } |
1502 | 0 | remote_domain_id = atoi(argv[optind]); |
1503 | 0 | remote_domain_name = argv[optind+1]; |
1504 | 0 | if (argc - optind >= 3) |
1505 | 0 | default_user = argv[optind+2]; |
1506 | 0 | init(remote_domain_id); |
1507 | |
|
1508 | 0 | sigemptyset(&selectmask); |
1509 | 0 | sigaddset(&selectmask, SIGCHLD); |
1510 | 0 | sigprocmask(SIG_BLOCK, &selectmask, NULL); |
1511 | 0 | sigemptyset(&selectmask); |
1512 | | |
1513 | | /* |
1514 | | * The main event loop. Waits for one of the following events: |
1515 | | * - message from client |
1516 | | * - message from agent |
1517 | | * - new client |
1518 | | * - child exited |
1519 | | */ |
1520 | 0 | while (!terminate_requested) { |
1521 | 0 | struct timespec timeout = { 1, 0 }; |
1522 | 0 | int ret; |
1523 | |
|
1524 | 0 | if (child_exited) |
1525 | 0 | reap_children(); |
1526 | |
|
1527 | 0 | size_t nfds = 0; |
1528 | 0 | struct pollfd fds[MAX_CLIENTS + 2]; |
1529 | 0 | fds[nfds++] = (struct pollfd) { libvchan_fd_for_select(vchan), POLLIN | POLLHUP, 0 }; |
1530 | 0 | if (libvchan_buffer_space(vchan) > (int)sizeof(struct msg_header)) { |
1531 | 0 | assert(max_client_fd < MAX_CLIENTS); |
1532 | | // vchan not full, read from clients |
1533 | 0 | fds[nfds++] = (struct pollfd) { qrexec_daemon_unix_socket_fd, POLLIN | POLLHUP, 0 }; |
1534 | 0 | for (i = 0; i <= max_client_fd; i++) { |
1535 | 0 | if (clients[i].state != CLIENT_INVALID) |
1536 | 0 | fds[nfds++] = (struct pollfd) { i, POLLIN | POLLHUP, 0 }; |
1537 | 0 | } |
1538 | 0 | } |
1539 | | |
1540 | 0 | ret = ppoll_vchan(vchan, fds, nfds, &timeout, &selectmask); |
1541 | 0 | if (ret < 0) { |
1542 | 0 | if (errno == EINTR) |
1543 | 0 | continue; |
1544 | 0 | PERROR("ppoll"); |
1545 | 0 | return 1; |
1546 | 0 | } |
1547 | | |
1548 | 0 | if (!libvchan_is_open(vchan)) { |
1549 | 0 | LOG(WARNING, "qrexec-agent has disconnected"); |
1550 | 0 | if (handle_agent_restart(remote_domain_id) < 0) { |
1551 | 0 | LOG(ERROR, "Failed to reconnect to qrexec-agent, terminating"); |
1552 | 0 | return 1; |
1553 | 0 | } |
1554 | | /* rdset may be outdated at this point, calculate it again. */ |
1555 | 0 | continue; |
1556 | 0 | } |
1557 | | |
1558 | 0 | if (nfds > 1 && fds[1].revents) |
1559 | 0 | handle_new_client(); |
1560 | |
|
1561 | 0 | while (libvchan_data_ready(vchan)) |
1562 | 0 | handle_message_from_agent(); |
1563 | |
|
1564 | 0 | for (size_t i = 2; i < nfds; i++) { |
1565 | 0 | if (fds[i].revents) |
1566 | 0 | handle_message_from_client(fds[i].fd); |
1567 | 0 | } |
1568 | 0 | } |
1569 | | |
1570 | 0 | if (vchan) |
1571 | 0 | libvchan_close(vchan); |
1572 | |
|
1573 | 0 | return 0; |
1574 | 0 | } |
1575 | | |
1576 | | // vim:ts=4:sw=4:et: |