qrexec: add option to wait for VM-VM connection termination

Normally when qrexec-client setup VM-VM connection it exits
immediatelly. But it may be useful to wait for the connection to
terminate - for example to cleanup DispVM.

qrexec-daemon (the one that allocated vchan port) do receive such
notification, so expose such option to qrexec-client.

QubesOS/qubes-issues#2253
pull/26/head
Marek Marczykowski-Górecki 8 years ago
parent 9192bb0d44
commit 849b295384
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

@ -537,11 +537,12 @@ static void select_loop(libvchan_t *vchan)
static void usage(char *name) static void usage(char *name)
{ {
fprintf(stderr, fprintf(stderr,
"usage: %s [-w timeout] [-t] [-T] -d domain_name [" "usage: %s [-w timeout] [-W] [-t] [-T] -d domain_name ["
"-l local_prog|" "-l local_prog|"
"-c request_id,src_domain_name,src_domain_id|" "-c request_id,src_domain_name,src_domain_id|"
"-e] remote_cmdline\n" "-e] remote_cmdline\n"
"-e means exit after sending cmd,\n" "-e means exit after sending cmd,\n"
"-W waits for connection end even in case of VM-VM (-c)\n"
"-t enables replacing ESC character with '_' in command output, -T is the same for stderr\n" "-t enables replacing ESC character with '_' in command output, -T is the same for stderr\n"
"-c: connect to existing process (response to trigger service call)\n" "-c: connect to existing process (response to trigger service call)\n"
"-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n", "-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n",
@ -641,6 +642,7 @@ int main(int argc, char **argv)
int msg_type; int msg_type;
int s; int s;
int just_exec = 0; int just_exec = 0;
int wait_connection_end = 0;
int connect_existing = 0; int connect_existing = 0;
char *local_cmdline = NULL; char *local_cmdline = NULL;
char *remote_cmdline = NULL; char *remote_cmdline = NULL;
@ -649,7 +651,7 @@ int main(int argc, char **argv)
int src_domain_id = 0; /* if not -c given, the process is run in dom0 */ int src_domain_id = 0; /* if not -c given, the process is run in dom0 */
int connection_timeout = 5; int connection_timeout = 5;
struct service_params svc_params; struct service_params svc_params;
while ((opt = getopt(argc, argv, "d:l:ec:tTw:")) != -1) { while ((opt = getopt(argc, argv, "d:l:ec:tTw:W")) != -1) {
switch (opt) { switch (opt) {
case 'd': case 'd':
domname = strdup(optarg); domname = strdup(optarg);
@ -674,6 +676,9 @@ int main(int argc, char **argv)
case 'w': case 'w':
connection_timeout = atoi(optarg); connection_timeout = atoi(optarg);
break; break;
case 'W':
wait_connection_end = 1;
break;
default: default:
usage(argv[0]); usage(argv[0]);
} }
@ -749,13 +754,25 @@ int main(int argc, char **argv)
strlen(remote_cmdline) + 1, strlen(remote_cmdline) + 1,
&data_domain, &data_domain,
&data_port); &data_port);
close(s); if (wait_connection_end && connect_existing)
/* save socket fd, 's' will be reused for the other qrexec-daemon
* connection */
wait_connection_end = s;
else
close(s);
setenv("QREXEC_REMOTE_DOMAIN", domname, 1); setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
prepare_local_fds(local_cmdline); prepare_local_fds(local_cmdline);
if (connect_existing) { if (connect_existing) {
s = connect_unix_socket(src_domain_name); s = connect_unix_socket(src_domain_name);
send_service_connect(s, request_id, data_domain, data_port); send_service_connect(s, request_id, data_domain, data_port);
close(s); close(s);
if (wait_connection_end) {
/* wait for EOF */
fd_set read_fd;
FD_ZERO(&read_fd);
FD_SET(wait_connection_end, &read_fd);
select(wait_connection_end+1, &read_fd, NULL, NULL, 0);
}
} else { } else {
data_vchan = libvchan_server_init(data_domain, data_port, data_vchan = libvchan_server_init(data_domain, data_port,
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE); VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);

@ -71,6 +71,12 @@ int policy_pending_max = -1;
* either VCHAN_PORT_* or remote domain id for used port */ * either VCHAN_PORT_* or remote domain id for used port */
int used_vchan_ports[MAX_CLIENTS]; int used_vchan_ports[MAX_CLIENTS];
/* notify client (close its connection) when connection initiated by it was
* terminated - used by qrexec-policy to cleanup (disposable) VM; indexed with
* vchan port number relative to VCHAN_BASE_DATA_PORT; stores fd of given
* client or -1 if none requested */
int vchan_port_notify_client[MAX_CLIENTS];
int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table
int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor
const char *default_user = "user"; const char *default_user = "user";
@ -312,6 +318,7 @@ void init(int xid)
clients[i].state = CLIENT_INVALID; clients[i].state = CLIENT_INVALID;
policy_pending[i].pid = 0; policy_pending[i].pid = 0;
used_vchan_ports[i] = VCHAN_PORT_UNUSED; used_vchan_ports[i] = VCHAN_PORT_UNUSED;
vchan_port_notify_client[i] = VCHAN_PORT_UNUSED;
} }
/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */ /* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
@ -358,14 +365,6 @@ static int allocate_vchan_port(int new_state)
return -1; return -1;
} }
static void release_vchan_port(int port, int expected_remote_id)
{
/* release only if was reserved for connection to given domain */
if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) {
used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED;
}
}
static void handle_new_client() static void handle_new_client()
{ {
int fd = do_accept(qrexec_daemon_unix_socket_fd); int fd = do_accept(qrexec_daemon_unix_socket_fd);
@ -387,8 +386,25 @@ static void handle_new_client()
static void terminate_client(int fd) static void terminate_client(int fd)
{ {
int port;
clients[fd].state = CLIENT_INVALID; clients[fd].state = CLIENT_INVALID;
close(fd); close(fd);
/* if client requested vchan connection end notify, cancel it */
for (port = 0; port < MAX_CLIENTS; port++) {
if (vchan_port_notify_client[port] == fd)
vchan_port_notify_client[port] = VCHAN_PORT_UNUSED;
}
}
static void release_vchan_port(int port, int expected_remote_id)
{
/* release only if was reserved for connection to given domain */
if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) {
used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED;
/* notify client if requested - it will clear notification request */
if (vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT] != VCHAN_PORT_UNUSED)
terminate_client(vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT]);
}
} }
static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr) static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr)
@ -433,6 +449,8 @@ static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr)
terminate_client(fd); terminate_client(fd);
return 0; return 0;
} }
/* notify the client when this connection got terminated */
vchan_port_notify_client[params.connect_port-VCHAN_BASE_DATA_PORT] = fd;
client_params.connect_port = params.connect_port; client_params.connect_port = params.connect_port;
client_params.connect_domain = remote_domain_id; client_params.connect_domain = remote_domain_id;
hdr->len = sizeof(client_params); hdr->len = sizeof(client_params);

Loading…
Cancel
Save