qrexec: new protocol - direct data vchan connections
This commit is contained in:
parent
0ba692c85a
commit
6efbbb88da
@ -24,9 +24,11 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
#include "qrexec.h"
|
#include "qrexec.h"
|
||||||
#include "libqrexec-utils.h"
|
#include "libqrexec-utils.h"
|
||||||
|
|
||||||
@ -34,7 +36,103 @@
|
|||||||
int replace_esc_stdout = 0;
|
int replace_esc_stdout = 0;
|
||||||
int replace_esc_stderr = 0;
|
int replace_esc_stderr = 0;
|
||||||
|
|
||||||
int connect_unix_socket(const char *domname)
|
#define VCHAN_BUFFER_SIZE 65536
|
||||||
|
|
||||||
|
int local_stdin_fd, local_stdout_fd;
|
||||||
|
pid_t local_pid = 0;
|
||||||
|
/* flag if this is "remote" end of service call. In this case swap STDIN/STDOUT
|
||||||
|
* msg types and send exit code at the end */
|
||||||
|
int is_service = 0;
|
||||||
|
int child_exited = 0;
|
||||||
|
|
||||||
|
static int handle_agent_handshake(libvchan_t *vchan, int remote_send_first)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct peer_info info;
|
||||||
|
int who = 0; // even - send to remote, odd - receive from remote
|
||||||
|
|
||||||
|
while (who < 2) {
|
||||||
|
if ((who+remote_send_first) & 1) {
|
||||||
|
if (!read_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
||||||
|
perror("daemon handshake");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
||||||
|
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!read_vchan_all(vchan, &info, sizeof(info))) {
|
||||||
|
perror("daemon handshake");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
||||||
|
fprintf(stderr, "Incompatible daemon protocol version "
|
||||||
|
"(daemon %d, client %d)\n",
|
||||||
|
info.version, QREXEC_PROTOCOL_VERSION);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hdr.type = MSG_HELLO;
|
||||||
|
hdr.len = sizeof(info);
|
||||||
|
info.version = QREXEC_PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
if (!write_vchan_all(vchan, &hdr, sizeof(hdr))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!write_vchan_all(vchan, &info, sizeof(info))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
who++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_daemon_handshake(int fd)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct peer_info info;
|
||||||
|
|
||||||
|
/* daemon send MSG_HELLO first */
|
||||||
|
if (!read_all(fd, &hdr, sizeof(hdr))) {
|
||||||
|
perror("daemon handshake");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
||||||
|
fprintf(stderr, "Invalid daemon MSG_HELLO\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!read_all(fd, &info, sizeof(info))) {
|
||||||
|
perror("daemon handshake");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
||||||
|
fprintf(stderr, "Incompatible daemon protocol version "
|
||||||
|
"(daemon %d, client %d)\n",
|
||||||
|
info.version, QREXEC_PROTOCOL_VERSION);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr.type = MSG_HELLO;
|
||||||
|
hdr.len = sizeof(info);
|
||||||
|
info.version = QREXEC_PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
if (!write_all(fd, &hdr, sizeof(hdr))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO hdr to daemon\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!write_all(fd, &info, sizeof(info))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO to daemon\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connect_unix_socket(const char *domname)
|
||||||
{
|
{
|
||||||
int s, len;
|
int s, len;
|
||||||
struct sockaddr_un remote;
|
struct sockaddr_un remote;
|
||||||
@ -52,17 +150,24 @@ int connect_unix_socket(const char *domname)
|
|||||||
perror("connect");
|
perror("connect");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
if (handle_daemon_handshake(s) < 0)
|
||||||
|
exit(1);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sigchld_handler(int x)
|
||||||
|
{
|
||||||
|
child_exited = 1;
|
||||||
|
signal(SIGCHLD, sigchld_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* called from do_fork_exec */
|
||||||
void do_exec(const char *prog)
|
void do_exec(const char *prog)
|
||||||
{
|
{
|
||||||
execl("/bin/bash", "bash", "-c", prog, NULL);
|
execl("/bin/bash", "bash", "-c", prog, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int local_stdin_fd, local_stdout_fd;
|
static void do_exit(int code)
|
||||||
|
|
||||||
void do_exit(int code)
|
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
// sever communication lines; wait for child, if any
|
// sever communication lines; wait for child, if any
|
||||||
@ -74,55 +179,128 @@ void do_exit(int code)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void prepare_local_fds(const char *cmdline)
|
static void prepare_local_fds(char *cmdline)
|
||||||
{
|
{
|
||||||
int pid;
|
|
||||||
if (!cmdline) {
|
if (!cmdline) {
|
||||||
local_stdin_fd = 1;
|
local_stdin_fd = 1;
|
||||||
local_stdout_fd = 0;
|
local_stdout_fd = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
do_fork_exec(cmdline, &pid, &local_stdin_fd, &local_stdout_fd,
|
signal(SIGCHLD, sigchld_handler);
|
||||||
|
do_fork_exec(cmdline, &local_pid, &local_stdin_fd, &local_stdout_fd,
|
||||||
NULL);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ask the daemon to allocate vchan port */
|
||||||
void send_cmdline(int s, int type, const char *cmdline)
|
static void negotiate_connection_params(int s, int other_domid, int type,
|
||||||
|
void *cmdline_param, int cmdline_size,
|
||||||
|
int *data_domain, int *data_port)
|
||||||
{
|
{
|
||||||
struct client_header hdr;
|
struct msg_header hdr;
|
||||||
|
struct exec_params params;
|
||||||
hdr.type = type;
|
hdr.type = type;
|
||||||
hdr.len = strlen(cmdline) + 1;
|
hdr.len = sizeof(params) + cmdline_size;
|
||||||
|
params.connect_domain = other_domid;
|
||||||
|
params.connect_port = 0;
|
||||||
if (!write_all(s, &hdr, sizeof(hdr))
|
if (!write_all(s, &hdr, sizeof(hdr))
|
||||||
|| !write_all(s, cmdline, hdr.len)) {
|
|| !write_all(s, ¶ms, sizeof(params))
|
||||||
|
|| !write_all(s, cmdline_param, cmdline_size)) {
|
||||||
|
perror("write daemon");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
/* the daemon will respond with the same message with connect_port filled
|
||||||
|
* and empty cmdline */
|
||||||
|
if (!read_all(s, &hdr, sizeof(hdr))) {
|
||||||
|
perror("read daemon");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
assert(hdr.type == type);
|
||||||
|
if (hdr.len != sizeof(params)) {
|
||||||
|
fprintf(stderr, "Invalid response for 0x%x\n", type);
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
if (!read_all(s, ¶ms, sizeof(params))) {
|
||||||
|
perror("read daemon");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
*data_port = params.connect_port;
|
||||||
|
*data_domain = params.connect_domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_service_connect(int s, char *conn_ident,
|
||||||
|
int connect_domain, int connect_port)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct exec_params exec_params;
|
||||||
|
struct service_params srv_params;
|
||||||
|
|
||||||
|
hdr.type = MSG_SERVICE_CONNECT;
|
||||||
|
hdr.len = sizeof(exec_params) + sizeof(srv_params);
|
||||||
|
|
||||||
|
exec_params.connect_domain = connect_domain;
|
||||||
|
exec_params.connect_port = connect_port;
|
||||||
|
strncpy(srv_params.ident, conn_ident, sizeof(srv_params.ident));
|
||||||
|
|
||||||
|
if (!write_all(s, &hdr, sizeof(hdr))
|
||||||
|
|| !write_all(s, &exec_params, sizeof(exec_params))
|
||||||
|
|| !write_all(s, &srv_params, sizeof(srv_params))) {
|
||||||
perror("write daemon");
|
perror("write daemon");
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_input(int s)
|
static void send_exit_code(libvchan_t *vchan, int status)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
|
||||||
|
hdr.type = MSG_DATA_EXIT_CODE;
|
||||||
|
hdr.len = sizeof(int);
|
||||||
|
if (libvchan_send(vchan, &hdr, sizeof(hdr)) < sizeof(hdr)) {
|
||||||
|
fprintf(stderr, "Failed to write exit code to the agent\n");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
if (libvchan_send(vchan, &status, sizeof(status)) < sizeof(status)) {
|
||||||
|
fprintf(stderr, "Failed to write exit code(2) to the agent\n");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_input(libvchan_t *vchan)
|
||||||
{
|
{
|
||||||
char buf[MAX_DATA_CHUNK];
|
char buf[MAX_DATA_CHUNK];
|
||||||
int ret;
|
int ret;
|
||||||
|
struct msg_header hdr;
|
||||||
|
|
||||||
ret = read(local_stdout_fd, buf, sizeof(buf));
|
ret = read(local_stdout_fd, buf, sizeof(buf));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
perror("read");
|
perror("read");
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
|
hdr.type = is_service ? MSG_DATA_STDOUT : MSG_DATA_STDIN;
|
||||||
|
hdr.len = ret;
|
||||||
|
if (libvchan_send(vchan, &hdr, sizeof(hdr)) < sizeof(hdr)) {
|
||||||
|
fprintf(stderr, "Failed to write STDIN data to the agent\n");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
close(local_stdout_fd);
|
close(local_stdout_fd);
|
||||||
local_stdout_fd = -1;
|
local_stdout_fd = -1;
|
||||||
shutdown(s, SHUT_WR);
|
|
||||||
if (local_stdin_fd == -1) {
|
if (local_stdin_fd == -1) {
|
||||||
// if pipe in opposite direction already closed, no need to stay alive
|
// if pipe in opposite direction already closed, no need to stay alive
|
||||||
|
if (is_service && local_pid == 0) {
|
||||||
|
/* if this is "remote" service end and no real local process
|
||||||
|
* exists (using own stdin/out) send also fake exit code */
|
||||||
|
send_exit_code(vchan, 0);
|
||||||
|
}
|
||||||
do_exit(0);
|
do_exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!write_all(s, buf, ret)) {
|
if (!write_vchan_all(vchan, buf, ret)) {
|
||||||
if (errno == EPIPE) {
|
if (!libvchan_is_open(vchan)) {
|
||||||
// daemon disconnected its end of socket, so no future data will be
|
// agent disconnected its end of socket, so no future data will be
|
||||||
// send there; there is no sense to read from child stdout
|
// send there; there is no sense to read from child stdout
|
||||||
//
|
//
|
||||||
// since AF_UNIX socket is buffered it doesn't mean all data was
|
// since vchan socket is buffered it doesn't mean all data was
|
||||||
// received from the agent
|
// received from the agent
|
||||||
close(local_stdout_fd);
|
close(local_stdout_fd);
|
||||||
local_stdout_fd = -1;
|
local_stdout_fd = -1;
|
||||||
@ -131,11 +309,11 @@ void handle_input(int s)
|
|||||||
// make sense to process the data from the daemon
|
// make sense to process the data from the daemon
|
||||||
//
|
//
|
||||||
// we don't know real exit VM process code (exiting here, before
|
// we don't know real exit VM process code (exiting here, before
|
||||||
// MSG_SERVER_TO_CLIENT_EXIT_CODE message)
|
// MSG_DATA_EXIT_CODE message)
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
perror("write daemon");
|
perror("write agent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,31 +325,33 @@ void do_replace_esc(char *buf, int len) {
|
|||||||
buf[i] = '_';
|
buf[i] = '_';
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_daemon_data(int s)
|
static void handle_vchan_data(libvchan_t *vchan)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
struct client_header hdr;
|
struct msg_header hdr;
|
||||||
char buf[MAX_DATA_CHUNK], *bufptr=buf;
|
char buf[MAX_DATA_CHUNK];
|
||||||
|
|
||||||
if (!read_all(s, &hdr, sizeof hdr)) {
|
if (libvchan_recv(vchan, &hdr, sizeof hdr) < 0) {
|
||||||
perror("read daemon");
|
perror("read vchan");
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
if (hdr.len > MAX_DATA_CHUNK) {
|
if (hdr.len > MAX_DATA_CHUNK) {
|
||||||
fprintf(stderr, "client_header.len=%d\n", hdr.len);
|
fprintf(stderr, "client_header.len=%d\n", hdr.len);
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
if (!read_all(s, buf, hdr.len)) {
|
if (!read_vchan_all(vchan, buf, hdr.len)) {
|
||||||
perror("read daemon");
|
perror("read daemon");
|
||||||
do_exit(1);
|
do_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (hdr.type) {
|
switch (hdr.type) {
|
||||||
case MSG_SERVER_TO_CLIENT_STDOUT:
|
/* both directions because we can serve as either end of service call */
|
||||||
if (replace_esc_stdout)
|
case MSG_DATA_STDIN:
|
||||||
do_replace_esc(buf, hdr.len);
|
case MSG_DATA_STDOUT:
|
||||||
if (local_stdin_fd == -1)
|
if (local_stdin_fd == -1)
|
||||||
break;
|
break;
|
||||||
|
if (replace_esc_stdout)
|
||||||
|
do_replace_esc(buf, hdr.len);
|
||||||
if (hdr.len == 0) {
|
if (hdr.len == 0) {
|
||||||
close(local_stdin_fd);
|
close(local_stdin_fd);
|
||||||
local_stdin_fd = -1;
|
local_stdin_fd = -1;
|
||||||
@ -186,13 +366,14 @@ void handle_daemon_data(int s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MSG_SERVER_TO_CLIENT_STDERR:
|
case MSG_DATA_STDERR:
|
||||||
if (replace_esc_stderr)
|
if (replace_esc_stderr)
|
||||||
do_replace_esc(buf, hdr.len);
|
do_replace_esc(buf, hdr.len);
|
||||||
write_all(2, buf, hdr.len);
|
write_all(2, buf, hdr.len);
|
||||||
break;
|
break;
|
||||||
case MSG_SERVER_TO_CLIENT_EXIT_CODE:
|
case MSG_DATA_EXIT_CODE:
|
||||||
status = *(unsigned int *) bufptr;
|
libvchan_close(vchan);
|
||||||
|
status = *(unsigned int *) buf;
|
||||||
if (WIFEXITED(status))
|
if (WIFEXITED(status))
|
||||||
do_exit(WEXITSTATUS(status));
|
do_exit(WEXITSTATUS(status));
|
||||||
else
|
else
|
||||||
@ -204,72 +385,142 @@ void handle_daemon_data(int s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// perhaps we could save a syscall if we include both sides in both
|
static void check_child_status(libvchan_t *vchan)
|
||||||
// rdset and wrset; to be investigated
|
|
||||||
void handle_daemon_only_until_writable(int s)
|
|
||||||
{
|
{
|
||||||
fd_set rdset, wrset;
|
pid_t pid;
|
||||||
|
int status;
|
||||||
|
|
||||||
do {
|
pid = waitpid(local_pid, &status, WNOHANG);
|
||||||
FD_ZERO(&rdset);
|
if (pid < 0) {
|
||||||
FD_ZERO(&wrset);
|
perror("waitpid");
|
||||||
FD_SET(s, &rdset);
|
do_exit(1);
|
||||||
FD_SET(s, &wrset);
|
}
|
||||||
|
if (pid == 0 || !WIFEXITED(status))
|
||||||
if (select(s + 1, &rdset, &wrset, NULL, NULL) < 0) {
|
return;
|
||||||
perror("select");
|
if (is_service)
|
||||||
do_exit(1);
|
send_exit_code(vchan, WEXITSTATUS(status));
|
||||||
}
|
do_exit(status);
|
||||||
if (FD_ISSET(s, &rdset))
|
|
||||||
handle_daemon_data(s);
|
|
||||||
} while (!FD_ISSET(s, &wrset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void select_loop(int s)
|
static void select_loop(libvchan_t *vchan)
|
||||||
{
|
{
|
||||||
fd_set select_set;
|
fd_set select_set;
|
||||||
int max;
|
int max_fd;
|
||||||
|
int ret;
|
||||||
|
int vchan_fd;
|
||||||
|
sigset_t selectmask;
|
||||||
|
struct timespec zero_timeout = { 0, 0 };
|
||||||
|
|
||||||
|
sigemptyset(&selectmask);
|
||||||
|
sigaddset(&selectmask, SIGCHLD);
|
||||||
|
sigprocmask(SIG_BLOCK, &selectmask, NULL);
|
||||||
|
sigemptyset(&selectmask);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
handle_daemon_only_until_writable(s);
|
vchan_fd = libvchan_fd_for_select(vchan);
|
||||||
FD_ZERO(&select_set);
|
FD_ZERO(&select_set);
|
||||||
FD_SET(s, &select_set);
|
FD_SET(vchan_fd, &select_set);
|
||||||
max = s;
|
max_fd = vchan_fd;
|
||||||
if (local_stdout_fd != -1) {
|
if (local_stdout_fd != -1 && libvchan_buffer_space(vchan)) {
|
||||||
FD_SET(local_stdout_fd, &select_set);
|
FD_SET(local_stdout_fd, &select_set);
|
||||||
if (s < local_stdout_fd)
|
if (local_stdout_fd > max_fd)
|
||||||
max = local_stdout_fd;
|
max_fd = local_stdout_fd;
|
||||||
}
|
}
|
||||||
if (select(max + 1, &select_set, NULL, NULL, NULL) < 0) {
|
if (child_exited)
|
||||||
perror("select");
|
check_child_status(vchan);
|
||||||
do_exit(1);
|
if (libvchan_data_ready(vchan) > 0) {
|
||||||
|
/* check for other FDs, but exit immediately */
|
||||||
|
ret = pselect(max_fd + 1, &select_set, NULL, NULL,
|
||||||
|
&zero_timeout, &selectmask);
|
||||||
|
} else
|
||||||
|
ret = pselect(max_fd + 1, &select_set, NULL, NULL,
|
||||||
|
NULL, &selectmask);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR && local_pid > 0) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
perror("select");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (FD_ISSET(s, &select_set))
|
if (FD_ISSET(vchan_fd, &select_set))
|
||||||
handle_daemon_data(s);
|
libvchan_wait(vchan);
|
||||||
|
while (libvchan_data_ready(vchan))
|
||||||
|
handle_vchan_data(vchan);
|
||||||
|
|
||||||
if (local_stdout_fd != -1
|
if (local_stdout_fd != -1
|
||||||
&& FD_ISSET(local_stdout_fd, &select_set))
|
&& FD_ISSET(local_stdout_fd, &select_set))
|
||||||
handle_input(s);
|
handle_input(vchan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void usage(const char *name)
|
static void usage(char *name)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"usage: %s -d domain_num [-l local_prog] -e -t -T -c remote_cmdline\n"
|
"usage: %s [-t] [-T] -d domain_name ["
|
||||||
"-e means exit after sending cmd, -c: connect to existing process\n"
|
"-l local_prog|"
|
||||||
"-t enables replacing ESC character with '_' in command output, -T is the same for stderr\n",
|
"-c request_id,src_domain_name,src_domain_id|"
|
||||||
|
"-e] remote_cmdline\n"
|
||||||
|
"-e means exit after sending cmd,\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",
|
||||||
name);
|
name);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parse_connect(char *str, char **request_id,
|
||||||
|
char **src_domain_name, int *src_domain_id)
|
||||||
|
{
|
||||||
|
int i=0;
|
||||||
|
char *token = NULL;
|
||||||
|
char *separators = ",";
|
||||||
|
|
||||||
|
token = strtok(str, separators);
|
||||||
|
while (token)
|
||||||
|
{
|
||||||
|
switch (i)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
*request_id = token;
|
||||||
|
if (strlen(*request_id) >= sizeof(struct service_params)) {
|
||||||
|
fprintf(stderr, "Invalid -c parameter (request_id too long, max %lu)\n",
|
||||||
|
sizeof(struct service_params)-1);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
*src_domain_name = token;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
*src_domain_id = atoi(token);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Invalid -c parameter (should be: \"-c request_id,src_domain_name,src_domain_id\")\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
token = strtok(NULL, separators);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int opt;
|
int opt;
|
||||||
char *domname = NULL;
|
char *domname = NULL;
|
||||||
|
libvchan_t *data_vchan = NULL;
|
||||||
|
int data_port;
|
||||||
|
int data_domain;
|
||||||
|
int msg_type;
|
||||||
int s;
|
int s;
|
||||||
int just_exec = 0;
|
int just_exec = 0;
|
||||||
int connect_existing = 0;
|
int connect_existing = 0;
|
||||||
char *local_cmdline = NULL;
|
char *local_cmdline = NULL;
|
||||||
while ((opt = getopt(argc, argv, "d:l:ectT")) != -1) {
|
char *remote_cmdline = NULL;
|
||||||
|
char *request_id;
|
||||||
|
char *src_domain_name;
|
||||||
|
int src_domain_id = 0; /* if not -c given, the process is run in dom0 */
|
||||||
|
struct service_params svc_params;
|
||||||
|
while ((opt = getopt(argc, argv, "d:l:ec:tT")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'd':
|
case 'd':
|
||||||
domname = strdup(optarg);
|
domname = strdup(optarg);
|
||||||
@ -281,7 +532,9 @@ int main(int argc, char **argv)
|
|||||||
just_exec = 1;
|
just_exec = 1;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
|
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
|
||||||
connect_existing = 1;
|
connect_existing = 1;
|
||||||
|
is_service = 1;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
replace_esc_stdout = 1;
|
replace_esc_stdout = 1;
|
||||||
@ -295,24 +548,89 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
if (optind >= argc || !domname)
|
if (optind >= argc || !domname)
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
|
remote_cmdline = argv[optind];
|
||||||
|
|
||||||
register_exec_func(&do_exec);
|
register_exec_func(&do_exec);
|
||||||
|
|
||||||
s = connect_unix_socket(domname);
|
if (just_exec + connect_existing + (local_cmdline != 0) > 1) {
|
||||||
setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
|
fprintf(stderr, "ERROR: only one of -e, -l, -c can be specified\n");
|
||||||
prepare_local_fds(local_cmdline);
|
usage(argv[0]);
|
||||||
|
}
|
||||||
|
|
||||||
if (just_exec)
|
if (strcmp(domname, "dom0") == 0 && !connect_existing) {
|
||||||
send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC,
|
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
|
||||||
argv[optind]);
|
usage(argv[0]);
|
||||||
else {
|
}
|
||||||
int cmd;
|
|
||||||
if (connect_existing)
|
if (strcmp(domname, "dom0") == 0) {
|
||||||
cmd = MSG_CLIENT_TO_SERVER_CONNECT_EXISTING;
|
if (connect_existing) {
|
||||||
|
msg_type = MSG_SERVICE_CONNECT;
|
||||||
|
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident));
|
||||||
|
} else if (just_exec)
|
||||||
|
msg_type = MSG_JUST_EXEC;
|
||||||
else
|
else
|
||||||
cmd = MSG_CLIENT_TO_SERVER_EXEC_CMDLINE;
|
msg_type = MSG_EXEC_CMDLINE;
|
||||||
send_cmdline(s, cmd, argv[optind]);
|
setenv("QREXEC_REMOTE_DOMAIN", src_domain_name, 1);
|
||||||
select_loop(s);
|
s = connect_unix_socket(src_domain_name);
|
||||||
|
negotiate_connection_params(s,
|
||||||
|
0, /* dom0 */
|
||||||
|
msg_type,
|
||||||
|
connect_existing ? (void*)&svc_params : (void*)remote_cmdline,
|
||||||
|
connect_existing ? sizeof(svc_params) : strlen(remote_cmdline) + 1,
|
||||||
|
&data_domain,
|
||||||
|
&data_port);
|
||||||
|
|
||||||
|
prepare_local_fds(remote_cmdline);
|
||||||
|
if (connect_existing)
|
||||||
|
data_vchan = libvchan_client_init(data_domain, data_port);
|
||||||
|
else {
|
||||||
|
data_vchan = libvchan_server_init(data_domain, data_port,
|
||||||
|
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
|
||||||
|
while (data_vchan && !libvchan_is_open(data_vchan))
|
||||||
|
libvchan_wait(data_vchan);
|
||||||
|
}
|
||||||
|
if (!data_vchan) {
|
||||||
|
fprintf(stderr, "Failed to open data vchan connection\n");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
if (handle_agent_handshake(data_vchan, connect_existing) < 0)
|
||||||
|
do_exit(1);
|
||||||
|
select_loop(data_vchan);
|
||||||
|
} else {
|
||||||
|
if (just_exec)
|
||||||
|
msg_type = MSG_JUST_EXEC;
|
||||||
|
else
|
||||||
|
msg_type = MSG_EXEC_CMDLINE;
|
||||||
|
s = connect_unix_socket(domname);
|
||||||
|
negotiate_connection_params(s,
|
||||||
|
src_domain_id,
|
||||||
|
msg_type,
|
||||||
|
remote_cmdline,
|
||||||
|
strlen(remote_cmdline) + 1,
|
||||||
|
&data_domain,
|
||||||
|
&data_port);
|
||||||
|
close(s);
|
||||||
|
setenv("QREXEC_REMOTE_DOMAIN", domname, 1);
|
||||||
|
prepare_local_fds(local_cmdline);
|
||||||
|
if (connect_existing) {
|
||||||
|
s = connect_unix_socket(src_domain_name);
|
||||||
|
send_service_connect(s, request_id, data_domain, data_port);
|
||||||
|
close(s);
|
||||||
|
} else {
|
||||||
|
data_vchan = libvchan_server_init(data_domain, data_port,
|
||||||
|
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
|
||||||
|
if (!data_vchan) {
|
||||||
|
fprintf(stderr, "Failed to start data vchan server\n");
|
||||||
|
do_exit(1);
|
||||||
|
}
|
||||||
|
while (!libvchan_is_open(data_vchan))
|
||||||
|
libvchan_wait(data_vchan);
|
||||||
|
if (handle_agent_handshake(data_vchan, 0) < 0)
|
||||||
|
do_exit(1);
|
||||||
|
select_loop(data_vchan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vim:ts=4:sw=4:et:
|
||||||
|
@ -29,24 +29,33 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
#include "qrexec.h"
|
#include "qrexec.h"
|
||||||
#include "libqrexec-utils.h"
|
#include "libqrexec-utils.h"
|
||||||
|
|
||||||
enum client_flags {
|
enum client_state {
|
||||||
CLIENT_INVALID = 0, // table slot not used
|
CLIENT_INVALID = 0, // table slot not used
|
||||||
CLIENT_CMDLINE = 1, // waiting for cmdline from client
|
CLIENT_HELLO, // waiting for client hello
|
||||||
CLIENT_DATA = 2, // waiting for data from client
|
CLIENT_CMDLINE, // waiting for cmdline from client
|
||||||
CLIENT_DONT_READ = 4, // don't read from the client, the other side pipe is full, or EOF (additionally marked with CLIENT_EOF)
|
CLIENT_RUNNING // waiting for client termination (to release vchan port)
|
||||||
CLIENT_OUTQ_FULL = 8, // don't write to client, its stdin pipe is full
|
};
|
||||||
CLIENT_EOF = 16, // got EOF
|
|
||||||
CLIENT_EXITED = 32 // only send remaining data from client and remove from list
|
enum vchan_port_state {
|
||||||
|
VCHAN_PORT_UNUSED = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _client {
|
struct _client {
|
||||||
int state; // combination of above enum client_flags
|
int state; // enum client_state
|
||||||
struct buffer buffer; // buffered data to client, if any
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct _policy_pending {
|
||||||
|
pid_t pid;
|
||||||
|
struct service_params params;
|
||||||
|
int reserved_vchan_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VCHAN_BASE_DATA_PORT (VCHAN_BASE_PORT+1)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The "clients" array is indexed by client's fd.
|
The "clients" array is indexed by client's fd.
|
||||||
Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity.
|
Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity.
|
||||||
@ -55,6 +64,13 @@ struct _client {
|
|||||||
#define MAX_CLIENTS MAX_FDS
|
#define MAX_CLIENTS MAX_FDS
|
||||||
struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections
|
struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections
|
||||||
|
|
||||||
|
struct _policy_pending policy_pending[MAX_CLIENTS];
|
||||||
|
int policy_pending_max = -1;
|
||||||
|
|
||||||
|
/* indexed with vchan port number relative to VCHAN_BASE_DATA_PORT; stores
|
||||||
|
* either VCHAN_PORT_* or remote domain id for used port */
|
||||||
|
int used_vchan_ports[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";
|
||||||
@ -93,10 +109,10 @@ void sigchld_parent_handler(int UNUSED(x))
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sigchld_handler(int x);
|
static void sigchld_handler(int UNUSED(x));
|
||||||
|
|
||||||
const char *remote_domain_name; // guess what
|
char *remote_domain_name; // guess what
|
||||||
int remote_domain_xid; // guess what
|
int remote_domain_id;
|
||||||
|
|
||||||
void unlink_qrexec_socket()
|
void unlink_qrexec_socket()
|
||||||
{
|
{
|
||||||
@ -104,14 +120,15 @@ void unlink_qrexec_socket()
|
|||||||
char link_to_socket_name[strlen(remote_domain_name) + sizeof(socket_address)];
|
char link_to_socket_name[strlen(remote_domain_name) + sizeof(socket_address)];
|
||||||
|
|
||||||
snprintf(socket_address, sizeof(socket_address),
|
snprintf(socket_address, sizeof(socket_address),
|
||||||
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", remote_domain_xid);
|
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", remote_domain_id);
|
||||||
snprintf(link_to_socket_name, sizeof link_to_socket_name,
|
snprintf(link_to_socket_name, sizeof link_to_socket_name,
|
||||||
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", remote_domain_name);
|
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", remote_domain_name);
|
||||||
unlink(socket_address);
|
unlink(socket_address);
|
||||||
unlink(link_to_socket_name);
|
unlink(link_to_socket_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_vchan_error(const char *op) {
|
void handle_vchan_error(const char *op)
|
||||||
|
{
|
||||||
fprintf(stderr, "Error while vchan %s, exiting\n", op);
|
fprintf(stderr, "Error while vchan %s, exiting\n", op);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -137,6 +154,74 @@ int create_qrexec_socket(int domid, const char *domname)
|
|||||||
|
|
||||||
#define MAX_STARTUP_TIME_DEFAULT 60
|
#define MAX_STARTUP_TIME_DEFAULT 60
|
||||||
|
|
||||||
|
static void incompatible_protocol_error_message(
|
||||||
|
const char *domain_name, int remote_version)
|
||||||
|
{
|
||||||
|
char text[1024];
|
||||||
|
int ret;
|
||||||
|
struct stat buf;
|
||||||
|
ret=stat("/usr/bin/kdialog", &buf);
|
||||||
|
#define KDIALOG_CMD "kdialog --title 'Qrexec daemon' --warningyesno "
|
||||||
|
#define ZENITY_CMD "zenity --title 'Qrexec daemon' --question --text "
|
||||||
|
snprintf(text, sizeof(text),
|
||||||
|
"%s"
|
||||||
|
"'Domain %s uses incompatible qrexec protocol (%d instead of %d). "
|
||||||
|
"You need to update either dom0 or VM packages.\n"
|
||||||
|
"To access this VM console do not close this error message and call:\n"
|
||||||
|
"sudo xl console vmname'",
|
||||||
|
ret==0 ? KDIALOG_CMD : ZENITY_CMD,
|
||||||
|
domain_name, remote_version, QREXEC_PROTOCOL_VERSION);
|
||||||
|
#undef KDIALOG_CMD
|
||||||
|
#undef ZENITY_CMD
|
||||||
|
system(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_agent_hello(libvchan_t *ctrl, const char *domain_name)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct peer_info info;
|
||||||
|
|
||||||
|
if (libvchan_recv(ctrl, &hdr, sizeof(hdr)) < sizeof(hdr)) {
|
||||||
|
fprintf(stderr, "Failed to read agent HELLO hdr\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
||||||
|
fprintf(stderr, "Invalid HELLO packet received: type %d, len %d\n", hdr.type, hdr.len);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libvchan_recv(ctrl, &info, sizeof(info)) < sizeof(info)) {
|
||||||
|
fprintf(stderr, "Failed to read agent HELLO body\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
||||||
|
fprintf(stderr, "Incompatible agent protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
|
||||||
|
incompatible_protocol_error_message(domain_name, info.version);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send own HELLO */
|
||||||
|
/* those messages are the same as received from agent, but set it again for
|
||||||
|
* readability */
|
||||||
|
hdr.type = MSG_HELLO;
|
||||||
|
hdr.len = sizeof(info);
|
||||||
|
info.version = QREXEC_PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
if (libvchan_send(ctrl, &hdr, sizeof(hdr)) < sizeof(hdr)) {
|
||||||
|
fprintf(stderr, "Failed to send HELLO hdr to agent\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (libvchan_send(ctrl, &info, sizeof(info)) < sizeof(info)) {
|
||||||
|
fprintf(stderr, "Failed to send HELLO hdr to agent\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* do the preparatory tasks, needed before entering the main event loop */
|
/* do the preparatory tasks, needed before entering the main event loop */
|
||||||
void init(int xid)
|
void init(int xid)
|
||||||
{
|
{
|
||||||
@ -204,14 +289,15 @@ void init(int xid)
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
vchan = libvchan_client_init(xid, REXEC_PORT);
|
vchan = libvchan_client_init(xid, VCHAN_BASE_PORT);
|
||||||
if (!vchan) {
|
if (!vchan) {
|
||||||
perror("cannot connect to qrexec agent");
|
perror("cannot connect to qrexec agent");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
/* wait for connection */
|
if (handle_agent_hello(vchan, remote_domain_name) < 0) {
|
||||||
while (!libvchan_is_open(vchan))
|
exit(1);
|
||||||
libvchan_wait(vchan);
|
}
|
||||||
|
|
||||||
if (setgid(getgid()) < 0) {
|
if (setgid(getgid()) < 0) {
|
||||||
perror("setgid()");
|
perror("setgid()");
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -220,6 +306,14 @@ void init(int xid)
|
|||||||
perror("setuid()");
|
perror("setuid()");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* initialize clients state arrays */
|
||||||
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
||||||
|
clients[i].state = CLIENT_INVALID;
|
||||||
|
policy_pending[i].pid = 0;
|
||||||
|
used_vchan_ports[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 */
|
||||||
umask(0);
|
umask(0);
|
||||||
qrexec_daemon_unix_socket_fd =
|
qrexec_daemon_unix_socket_fd =
|
||||||
@ -231,58 +325,126 @@ void init(int xid)
|
|||||||
kill(getppid(), SIGUSR1); // let the parent know we are ready
|
kill(getppid(), SIGUSR1); // let the parent know we are ready
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_new_client(void)
|
static int send_client_hello(int fd)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct peer_info info;
|
||||||
|
|
||||||
|
hdr.type = MSG_HELLO;
|
||||||
|
hdr.len = sizeof(info);
|
||||||
|
info.version = QREXEC_PROTOCOL_VERSION;
|
||||||
|
|
||||||
|
if (!write_all(fd, &hdr, sizeof(hdr))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO hdr to client %d\n", fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!write_all(fd, &info, sizeof(info))) {
|
||||||
|
fprintf(stderr, "Failed to send MSG_HELLO to client %d\n", fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int allocate_vchan_port(int new_state)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
||||||
|
if (used_vchan_ports[i] == VCHAN_PORT_UNUSED) {
|
||||||
|
used_vchan_ports[i] = new_state;
|
||||||
|
return VCHAN_BASE_DATA_PORT+i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
int fd = do_accept(qrexec_daemon_unix_socket_fd);
|
int fd = do_accept(qrexec_daemon_unix_socket_fd);
|
||||||
if (fd >= MAX_CLIENTS) {
|
if (fd >= MAX_CLIENTS) {
|
||||||
fprintf(stderr, "too many clients ?\n");
|
fprintf(stderr, "too many clients ?\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
clients[fd].state = CLIENT_CMDLINE;
|
|
||||||
buffer_init(&clients[fd].buffer);
|
if (send_client_hello(fd) < 0) {
|
||||||
|
close(fd);
|
||||||
|
clients[fd].state = CLIENT_INVALID;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[fd].state = CLIENT_HELLO;
|
||||||
if (fd > max_client_fd)
|
if (fd > max_client_fd)
|
||||||
max_client_fd = fd;
|
max_client_fd = fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
void terminate_client_and_flush_data(int fd)
|
static void terminate_client(int fd)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
struct server_header s_hdr;
|
|
||||||
|
|
||||||
if (!(clients[fd].state & CLIENT_EXITED) && fork_and_flush_stdin(fd, &clients[fd].buffer))
|
|
||||||
children_count++;
|
|
||||||
close(fd);
|
|
||||||
clients[fd].state = CLIENT_INVALID;
|
clients[fd].state = CLIENT_INVALID;
|
||||||
buffer_free(&clients[fd].buffer);
|
close(fd);
|
||||||
if (max_client_fd == fd) {
|
|
||||||
for (i = fd; i >= 0 && clients[i].state == CLIENT_INVALID;
|
|
||||||
i--);
|
|
||||||
max_client_fd = i;
|
|
||||||
}
|
|
||||||
s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END;
|
|
||||||
s_hdr.client_id = fd;
|
|
||||||
s_hdr.len = 0;
|
|
||||||
if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0)
|
|
||||||
handle_vchan_error("send");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header
|
static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr)
|
||||||
*s_hdr)
|
|
||||||
{
|
{
|
||||||
int len = s_hdr->len;
|
struct exec_params params;
|
||||||
|
int len = hdr->len-sizeof(params);
|
||||||
char buf[len];
|
char buf[len];
|
||||||
int use_default_user = 0;
|
int use_default_user = 0;
|
||||||
if (!read_all(fd, buf, len)) {
|
|
||||||
terminate_client_and_flush_data(fd);
|
if (!read_all(fd, ¶ms, sizeof(params))) {
|
||||||
|
terminate_client(fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (!read_all(fd, buf, len)) {
|
||||||
|
terminate_client(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.connect_port) {
|
||||||
|
struct exec_params client_params;
|
||||||
|
/* allocate port and send it to the client */
|
||||||
|
params.connect_port = allocate_vchan_port(params.connect_domain);
|
||||||
|
if (params.connect_port <= 0) {
|
||||||
|
fprintf(stderr, "Failed to allocate new vchan port, too many clients?\n");
|
||||||
|
terminate_client(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
client_params.connect_port = params.connect_port;
|
||||||
|
client_params.connect_domain = remote_domain_id;
|
||||||
|
hdr->len = sizeof(client_params);
|
||||||
|
if (!write_all(fd, hdr, sizeof(*hdr))) {
|
||||||
|
terminate_client(fd);
|
||||||
|
release_vchan_port(params.connect_port, params.connect_domain);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!write_all(fd, &client_params, sizeof(client_params))) {
|
||||||
|
terminate_client(fd);
|
||||||
|
release_vchan_port(params.connect_port, params.connect_domain);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* restore original len value */
|
||||||
|
hdr->len = len+sizeof(params);
|
||||||
|
} else {
|
||||||
|
assert(params.connect_port >= VCHAN_BASE_DATA_PORT);
|
||||||
|
assert(params.connect_port < VCHAN_BASE_DATA_PORT+MAX_CLIENTS);
|
||||||
|
}
|
||||||
|
|
||||||
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
|
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
|
||||||
use_default_user = 1;
|
use_default_user = 1;
|
||||||
s_hdr->len -= default_user_keyword_len_without_colon; // -1 because of colon
|
hdr->len -= default_user_keyword_len_without_colon;
|
||||||
s_hdr->len += strlen(default_user);
|
hdr->len += strlen(default_user);
|
||||||
}
|
}
|
||||||
if (libvchan_send(vchan, s_hdr, sizeof(*s_hdr)) < 0)
|
if (libvchan_send(vchan, hdr, sizeof(*hdr)) < 0)
|
||||||
handle_vchan_error("send");
|
handle_vchan_error("send");
|
||||||
|
if (libvchan_send(vchan, ¶ms, sizeof(params)) < 0)
|
||||||
|
handle_vchan_error("send params");
|
||||||
if (use_default_user) {
|
if (use_default_user) {
|
||||||
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
|
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
|
||||||
handle_vchan_error("send default_user");
|
handle_vchan_error("send default_user");
|
||||||
@ -295,149 +457,82 @@ int get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_cmdline_message_from_client(int fd)
|
static void handle_cmdline_message_from_client(int fd)
|
||||||
{
|
{
|
||||||
struct client_header hdr;
|
struct msg_header hdr;
|
||||||
struct server_header s_hdr;
|
|
||||||
if (!read_all(fd, &hdr, sizeof hdr)) {
|
if (!read_all(fd, &hdr, sizeof hdr)) {
|
||||||
terminate_client_and_flush_data(fd);
|
terminate_client(fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (hdr.type) {
|
switch (hdr.type) {
|
||||||
case MSG_CLIENT_TO_SERVER_EXEC_CMDLINE:
|
case MSG_EXEC_CMDLINE:
|
||||||
s_hdr.type = MSG_SERVER_TO_AGENT_EXEC_CMDLINE;
|
case MSG_JUST_EXEC:
|
||||||
break;
|
case MSG_SERVICE_CONNECT:
|
||||||
case MSG_CLIENT_TO_SERVER_JUST_EXEC:
|
|
||||||
s_hdr.type = MSG_SERVER_TO_AGENT_JUST_EXEC;
|
|
||||||
break;
|
|
||||||
case MSG_CLIENT_TO_SERVER_CONNECT_EXISTING:
|
|
||||||
s_hdr.type = MSG_SERVER_TO_AGENT_CONNECT_EXISTING;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
terminate_client_and_flush_data(fd);
|
terminate_client(fd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_hdr.client_id = fd;
|
if (!handle_cmdline_body_from_client(fd, &hdr))
|
||||||
s_hdr.len = hdr.len;
|
|
||||||
if (!get_cmdline_body_from_client_and_pass_to_agent(fd, &s_hdr))
|
|
||||||
// client disconnected while sending cmdline, above call already
|
// client disconnected while sending cmdline, above call already
|
||||||
// cleaned up client info
|
// cleaned up client info
|
||||||
return;
|
return;
|
||||||
clients[fd].state = CLIENT_DATA;
|
clients[fd].state = CLIENT_RUNNING;
|
||||||
set_nonblock(fd); // so that we can detect full queue without blocking
|
}
|
||||||
if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC)
|
|
||||||
terminate_client_and_flush_data(fd);
|
|
||||||
|
|
||||||
|
static void handle_client_hello(int fd)
|
||||||
|
{
|
||||||
|
struct msg_header hdr;
|
||||||
|
struct peer_info info;
|
||||||
|
|
||||||
|
if (!read_all(fd, &hdr, sizeof hdr)) {
|
||||||
|
terminate_client(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
|
||||||
|
fprintf(stderr, "Invalid HELLO packet received from client %d: "
|
||||||
|
"type %d, len %d\n", fd, hdr.type, hdr.len);
|
||||||
|
terminate_client(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!read_all(fd, &info, sizeof info)) {
|
||||||
|
terminate_client(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.version != QREXEC_PROTOCOL_VERSION) {
|
||||||
|
fprintf(stderr, "Incompatible client protocol version (remote %d, local %d)\n", info.version, QREXEC_PROTOCOL_VERSION);
|
||||||
|
terminate_client(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clients[fd].state = CLIENT_CMDLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* handle data received from one of qrexec_client processes */
|
/* handle data received from one of qrexec_client processes */
|
||||||
void handle_message_from_client(int fd)
|
static void handle_message_from_client(int fd)
|
||||||
{
|
{
|
||||||
struct server_header s_hdr;
|
|
||||||
char buf[MAX_DATA_CHUNK];
|
char buf[MAX_DATA_CHUNK];
|
||||||
unsigned int len;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (clients[fd].state == CLIENT_CMDLINE) {
|
switch (clients[fd].state) {
|
||||||
handle_cmdline_message_from_client(fd);
|
case CLIENT_HELLO:
|
||||||
return;
|
handle_client_hello(fd);
|
||||||
}
|
return;
|
||||||
// We have already passed cmdline from client.
|
case CLIENT_CMDLINE:
|
||||||
// Now the client passes us raw data from its stdin.
|
handle_cmdline_message_from_client(fd);
|
||||||
len = libvchan_buffer_space(vchan);
|
return;
|
||||||
if (len <= sizeof s_hdr)
|
case CLIENT_RUNNING:
|
||||||
return;
|
// expected EOF
|
||||||
/* Read at most the amount of data that we have room for in vchan */
|
if (read(fd, buf, sizeof(buf)) != 0) {
|
||||||
ret = read(fd, buf, len - sizeof(s_hdr));
|
fprintf(stderr, "Unexpected data received from client %d\n", fd);
|
||||||
if (ret < 0) {
|
}
|
||||||
perror("read client");
|
terminate_client(fd);
|
||||||
terminate_client_and_flush_data(fd);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
s_hdr.client_id = fd;
|
|
||||||
s_hdr.len = ret;
|
|
||||||
s_hdr.type = MSG_SERVER_TO_AGENT_INPUT;
|
|
||||||
|
|
||||||
if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0)
|
|
||||||
handle_vchan_error("send hdr");
|
|
||||||
if (libvchan_send(vchan, buf, ret) < 0)
|
|
||||||
handle_vchan_error("send buf");
|
|
||||||
if (ret == 0) // EOF - so don't select() on this client
|
|
||||||
clients[fd].state |= CLIENT_DONT_READ | CLIENT_EOF;
|
|
||||||
if (clients[fd].state & CLIENT_EXITED)
|
|
||||||
//client already exited and all data sent - cleanup now
|
|
||||||
terminate_client_and_flush_data(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Called when there is buffered data for this client, and select() reports
|
|
||||||
* that client's pipe is writable; so we should be able to flush some
|
|
||||||
* buffered data.
|
|
||||||
*/
|
|
||||||
void write_buffered_data_to_client(int client_id)
|
|
||||||
{
|
|
||||||
switch (flush_client_data
|
|
||||||
(vchan, client_id, client_id, &clients[client_id].buffer)) {
|
|
||||||
case WRITE_STDIN_OK: // no more buffered data
|
|
||||||
clients[client_id].state &= ~CLIENT_OUTQ_FULL;
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_ERROR:
|
|
||||||
// do not write to this fd anymore
|
|
||||||
clients[client_id].state |= CLIENT_EXITED;
|
|
||||||
if (clients[client_id].state & CLIENT_EOF)
|
|
||||||
terminate_client_and_flush_data(client_id);
|
|
||||||
else
|
|
||||||
// client will be removed when read returns 0 (EOF)
|
|
||||||
// clear CLIENT_OUTQ_FULL flag to no select on this fd anymore
|
|
||||||
clients[client_id].state &= ~CLIENT_OUTQ_FULL;
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_BUFFERED: // no room for all data, don't clear CLIENT_OUTQ_FULL flag
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "unknown flush_client_data?\n");
|
fprintf(stderr, "Invalid client state %d\n", clients[fd].state);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* The header (hdr argument) is already built. Just read the raw data from
|
|
||||||
* the packet, and pass it along with the header to the client.
|
|
||||||
*/
|
|
||||||
void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_header
|
|
||||||
*hdr)
|
|
||||||
{
|
|
||||||
int len = hdr->len;
|
|
||||||
char buf[sizeof(*hdr) + len];
|
|
||||||
|
|
||||||
/* make both the header and data be consecutive in the buffer */
|
|
||||||
memcpy(buf, hdr, sizeof(*hdr));
|
|
||||||
if (libvchan_recv(vchan, buf + sizeof(*hdr), len) < 0)
|
|
||||||
handle_vchan_error("recv buf");
|
|
||||||
if (clients[client_id].state & CLIENT_EXITED)
|
|
||||||
// ignore data for no longer running client
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (write_stdin
|
|
||||||
(vchan, client_id, client_id, buf, len + sizeof(*hdr),
|
|
||||||
&clients[client_id].buffer)) {
|
|
||||||
case WRITE_STDIN_OK:
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_BUFFERED: // some data have been buffered
|
|
||||||
clients[client_id].state |= CLIENT_OUTQ_FULL;
|
|
||||||
break;
|
|
||||||
case WRITE_STDIN_ERROR:
|
|
||||||
// do not write to this fd anymore
|
|
||||||
clients[client_id].state |= CLIENT_EXITED;
|
|
||||||
// if already got EOF, remove client
|
|
||||||
if (clients[client_id].state & CLIENT_EOF)
|
|
||||||
terminate_client_and_flush_data(client_id);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "unknown write_stdin?\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The signal handler executes asynchronously; therefore all it should do is
|
* The signal handler executes asynchronously; therefore all it should do is
|
||||||
@ -447,42 +542,72 @@ void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_
|
|||||||
|
|
||||||
int child_exited;
|
int child_exited;
|
||||||
|
|
||||||
void sigchld_handler(int UNUSED(x))
|
static void sigchld_handler(int UNUSED(x))
|
||||||
{
|
{
|
||||||
child_exited = 1;
|
child_exited = 1;
|
||||||
signal(SIGCHLD, sigchld_handler);
|
signal(SIGCHLD, sigchld_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clean zombies, update children_count */
|
static void send_service_refused(libvchan_t *vchan, struct service_params *params) {
|
||||||
void reap_children(void)
|
struct msg_header hdr;
|
||||||
{
|
|
||||||
int status;
|
|
||||||
while (waitpid(-1, &status, WNOHANG) > 0)
|
|
||||||
children_count--;
|
|
||||||
child_exited = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* too many children - wait for one of them to terminate */
|
hdr.type = MSG_SERVICE_REFUSED;
|
||||||
void wait_for_child(void)
|
hdr.len = sizeof(*params);
|
||||||
{
|
|
||||||
int status;
|
|
||||||
waitpid(-1, &status, 0);
|
|
||||||
children_count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MAX_CHILDREN 10
|
if (libvchan_send(vchan, &hdr, sizeof(hdr)) < sizeof(hdr)) {
|
||||||
void check_children_count_and_wait_if_too_many(void)
|
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED hdr to agent\n");
|
||||||
{
|
exit(1);
|
||||||
if (children_count > MAX_CHILDREN) {
|
}
|
||||||
fprintf(stderr,
|
|
||||||
"max number of children reached, waiting for child exit...\n");
|
if (libvchan_send(vchan, params, sizeof(*params)) < sizeof(*params)) {
|
||||||
wait_for_child();
|
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED to agent\n");
|
||||||
fprintf(stderr, "now children_count=%d, continuing.\n",
|
exit(1);
|
||||||
children_count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sanitize_name(char * untrusted_s_signed)
|
/* clean zombies, check for denied service calls */
|
||||||
|
static void reap_children()
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
while ((pid=waitpid(-1, &status, WNOHANG)) > 0) {
|
||||||
|
/* FIXME: perhaps keep max(policy_pending) somewhere to optimize this
|
||||||
|
* search */
|
||||||
|
for (i = 0; i <= policy_pending_max; i++) {
|
||||||
|
if (policy_pending[i].pid == pid) {
|
||||||
|
status = WEXITSTATUS(status);
|
||||||
|
if (status != 0) {
|
||||||
|
send_service_refused(vchan, &policy_pending[i].params);
|
||||||
|
}
|
||||||
|
/* in case of allowed calls, we will do the rest in
|
||||||
|
* MSG_SERVICE_CONNECT from client handler */
|
||||||
|
policy_pending[i].pid = 0;
|
||||||
|
while (policy_pending_max > 0 &&
|
||||||
|
policy_pending[policy_pending_max].pid > 0)
|
||||||
|
policy_pending_max--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child_exited = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_policy_pending_slot() {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
||||||
|
if (policy_pending[i].pid == 0) {
|
||||||
|
if (i > policy_pending_max)
|
||||||
|
policy_pending_max = i;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sanitize_name(char * untrusted_s_signed)
|
||||||
{
|
{
|
||||||
unsigned char * untrusted_s;
|
unsigned char * untrusted_s;
|
||||||
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
|
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
|
||||||
@ -498,145 +623,128 @@ void sanitize_name(char * untrusted_s_signed)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0
|
#define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called when agent sends a message asking to execute a predefined command.
|
* Called when agent sends a message asking to execute a predefined command.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void handle_execute_predefined_command(void)
|
static void handle_execute_service(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
struct trigger_connect_params untrusted_params, params;
|
int policy_pending_slot;
|
||||||
|
pid_t pid;
|
||||||
|
struct trigger_service_params untrusted_params, params;
|
||||||
|
char remote_domain_id_str[10];
|
||||||
|
|
||||||
check_children_count_and_wait_if_too_many();
|
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
|
||||||
if (libvchan_recv(vchan, &untrusted_params, sizeof(params)) < 0)
|
|
||||||
handle_vchan_error("recv params");
|
handle_vchan_error("recv params");
|
||||||
|
|
||||||
/* sanitize start */
|
/* sanitize start */
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.exec_index);
|
ENSURE_NULL_TERMINATED(untrusted_params.service_name);
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.target_vmname);
|
ENSURE_NULL_TERMINATED(untrusted_params.target_domain);
|
||||||
ENSURE_NULL_TERMINATED(untrusted_params.process_fds.ident);
|
ENSURE_NULL_TERMINATED(untrusted_params.request_id.ident);
|
||||||
sanitize_name(untrusted_params.exec_index);
|
sanitize_name(untrusted_params.service_name);
|
||||||
sanitize_name(untrusted_params.target_vmname);
|
sanitize_name(untrusted_params.target_domain);
|
||||||
sanitize_name(untrusted_params.process_fds.ident);
|
sanitize_name(untrusted_params.request_id.ident);
|
||||||
params = untrusted_params;
|
params = untrusted_params;
|
||||||
/* sanitize end */
|
/* sanitize end */
|
||||||
|
|
||||||
switch (fork()) {
|
policy_pending_slot = find_policy_pending_slot();
|
||||||
|
if (policy_pending_slot < 0) {
|
||||||
|
fprintf(stderr, "Service request denied, too many pending requests\n");
|
||||||
|
send_service_refused(vchan, &untrusted_params.request_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pid=fork()) {
|
||||||
case -1:
|
case -1:
|
||||||
perror("fork");
|
perror("fork");
|
||||||
exit(1);
|
exit(1);
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
children_count++;
|
policy_pending[policy_pending_slot].pid = pid;
|
||||||
|
policy_pending[policy_pending_slot].params = untrusted_params.request_id;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (i = 3; i < MAX_FDS; i++)
|
for (i = 3; i < MAX_FDS; i++)
|
||||||
close(i);
|
close(i);
|
||||||
signal(SIGCHLD, SIG_DFL);
|
signal(SIGCHLD, SIG_DFL);
|
||||||
signal(SIGPIPE, SIG_DFL);
|
signal(SIGPIPE, SIG_DFL);
|
||||||
|
snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d",
|
||||||
|
remote_domain_id);
|
||||||
execl("/usr/lib/qubes/qrexec-policy", "qrexec-policy",
|
execl("/usr/lib/qubes/qrexec-policy", "qrexec-policy",
|
||||||
remote_domain_name, params.target_vmname,
|
remote_domain_id_str, remote_domain_name, params.target_domain,
|
||||||
params.exec_index, params.process_fds.ident, NULL);
|
params.service_name, params.request_id.ident, NULL);
|
||||||
perror("execl");
|
perror("execl");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_client_id_in_range(unsigned int untrusted_client_id)
|
static void handle_connection_terminated()
|
||||||
{
|
{
|
||||||
if (untrusted_client_id >= MAX_CLIENTS) {
|
struct exec_params untrusted_params, params;
|
||||||
fprintf(stderr, "from agent: client_id=%d\n",
|
|
||||||
untrusted_client_id);
|
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
|
||||||
|
handle_vchan_error("recv params");
|
||||||
|
/* sanitize start */
|
||||||
|
if (untrusted_params.connect_port < VCHAN_BASE_DATA_PORT ||
|
||||||
|
untrusted_params.connect_port >= VCHAN_BASE_DATA_PORT+MAX_CLIENTS) {
|
||||||
|
fprintf(stderr, "Invalid port in MSG_CONNECTION_TERMINATED (%d)\n",
|
||||||
|
untrusted_params.connect_port);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
/* untrusted_params.connect_domain even if invalid will not harm - in worst
|
||||||
|
* case the port will not be released */
|
||||||
|
params = untrusted_params;
|
||||||
|
/* sanitize end */
|
||||||
|
release_vchan_port(params.connect_port, params.connect_domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sanitize_message_from_agent(struct msg_header *untrusted_header)
|
||||||
void sanitize_message_from_agent(struct server_header *untrusted_header)
|
|
||||||
{
|
{
|
||||||
switch (untrusted_header->type) {
|
switch (untrusted_header->type) {
|
||||||
case MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING:
|
case MSG_TRIGGER_SERVICE:
|
||||||
break;
|
if (untrusted_header->len != sizeof(struct trigger_service_params)) {
|
||||||
case MSG_AGENT_TO_SERVER_STDOUT:
|
fprintf(stderr, "agent sent invalid MSG_TRIGGER_SERVICE packet\n");
|
||||||
case MSG_AGENT_TO_SERVER_STDERR:
|
|
||||||
case MSG_AGENT_TO_SERVER_EXIT_CODE:
|
|
||||||
check_client_id_in_range(untrusted_header->client_id);
|
|
||||||
if (untrusted_header->len > MAX_DATA_CHUNK) {
|
|
||||||
fprintf(stderr, "agent feeded %d of data bytes?\n",
|
|
||||||
untrusted_header->len);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case MSG_CONNECTION_TERMINATED:
|
||||||
case MSG_XOFF:
|
if (untrusted_header->len != sizeof(struct exec_params)) {
|
||||||
case MSG_XON:
|
fprintf(stderr, "agent sent invalid MSG_CONNECTION_TERMINATED packet\n");
|
||||||
check_client_id_in_range(untrusted_header->client_id);
|
exit(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "unknown mesage type %d from agent\n",
|
fprintf(stderr, "unknown mesage type 0x%x from agent\n",
|
||||||
untrusted_header->type);
|
untrusted_header->type);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_message_from_agent(void)
|
static void handle_message_from_agent(void)
|
||||||
{
|
{
|
||||||
struct client_header hdr;
|
struct msg_header hdr, untrusted_hdr;
|
||||||
struct server_header s_hdr, untrusted_s_hdr;
|
|
||||||
|
|
||||||
if (libvchan_recv(vchan, &untrusted_s_hdr, sizeof(untrusted_s_hdr)) < 0)
|
if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) < 0)
|
||||||
handle_vchan_error("recv hdr");
|
handle_vchan_error("recv hdr");
|
||||||
/* sanitize start */
|
/* sanitize start */
|
||||||
sanitize_message_from_agent(&untrusted_s_hdr);
|
sanitize_message_from_agent(&untrusted_hdr);
|
||||||
s_hdr = untrusted_s_hdr;
|
hdr = untrusted_hdr;
|
||||||
/* sanitize end */
|
/* sanitize end */
|
||||||
|
|
||||||
if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING) {
|
// fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id,
|
||||||
handle_execute_predefined_command();
|
// hdr.len);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s_hdr.type == MSG_XOFF) {
|
switch (hdr.type) {
|
||||||
clients[s_hdr.client_id].state |= CLIENT_DONT_READ;
|
case MSG_TRIGGER_SERVICE:
|
||||||
return;
|
handle_execute_service();
|
||||||
|
return;
|
||||||
|
case MSG_CONNECTION_TERMINATED:
|
||||||
|
handle_connection_terminated();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_hdr.type == MSG_XON) {
|
|
||||||
clients[s_hdr.client_id].state &= ~CLIENT_DONT_READ;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (s_hdr.type) {
|
|
||||||
case MSG_AGENT_TO_SERVER_STDOUT:
|
|
||||||
hdr.type = MSG_SERVER_TO_CLIENT_STDOUT;
|
|
||||||
break;
|
|
||||||
case MSG_AGENT_TO_SERVER_STDERR:
|
|
||||||
hdr.type = MSG_SERVER_TO_CLIENT_STDERR;
|
|
||||||
break;
|
|
||||||
case MSG_AGENT_TO_SERVER_EXIT_CODE:
|
|
||||||
hdr.type = MSG_SERVER_TO_CLIENT_EXIT_CODE;
|
|
||||||
break;
|
|
||||||
default: /* cannot happen, already sanitized */
|
|
||||||
fprintf(stderr, "from agent: type=%d\n", s_hdr.type);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
hdr.len = s_hdr.len;
|
|
||||||
if (clients[s_hdr.client_id].state == CLIENT_INVALID) {
|
|
||||||
// benefit of doubt - maybe client exited earlier
|
|
||||||
// just eat the packet data and continue
|
|
||||||
char buf[MAX_DATA_CHUNK];
|
|
||||||
if (libvchan_recv(vchan, buf, s_hdr.len) < 0)
|
|
||||||
handle_vchan_error("recv buf");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
get_packet_data_from_agent_and_pass_to_client(s_hdr.client_id,
|
|
||||||
&hdr);
|
|
||||||
if (s_hdr.type == MSG_AGENT_TO_SERVER_EXIT_CODE)
|
|
||||||
terminate_client_and_flush_data(s_hdr.client_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -645,23 +753,17 @@ void handle_message_from_agent(void)
|
|||||||
* to (because its pipe is full) to write_fdset. Return the highest used file
|
* to (because its pipe is full) to write_fdset. Return the highest used file
|
||||||
* descriptor number, needed for the first select() parameter.
|
* descriptor number, needed for the first select() parameter.
|
||||||
*/
|
*/
|
||||||
int fill_fdsets_for_select(fd_set * read_fdset, fd_set * write_fdset)
|
static int fill_fdsets_for_select(fd_set * read_fdset, fd_set * write_fdset)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
int max = -1;
|
int max = -1;
|
||||||
FD_ZERO(read_fdset);
|
FD_ZERO(read_fdset);
|
||||||
FD_ZERO(write_fdset);
|
FD_ZERO(write_fdset);
|
||||||
for (i = 0; i <= max_client_fd; i++) {
|
for (i = 0; i <= max_client_fd; i++) {
|
||||||
if (clients[i].state != CLIENT_INVALID
|
if (clients[i].state != CLIENT_INVALID) {
|
||||||
&& !(clients[i].state & CLIENT_DONT_READ)) {
|
|
||||||
FD_SET(i, read_fdset);
|
FD_SET(i, read_fdset);
|
||||||
max = i;
|
max = i;
|
||||||
}
|
}
|
||||||
if (clients[i].state != CLIENT_INVALID
|
|
||||||
&& clients[i].state & CLIENT_OUTQ_FULL) {
|
|
||||||
FD_SET(i, write_fdset);
|
|
||||||
max = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
FD_SET(qrexec_daemon_unix_socket_fd, read_fdset);
|
FD_SET(qrexec_daemon_unix_socket_fd, read_fdset);
|
||||||
if (qrexec_daemon_unix_socket_fd > max)
|
if (qrexec_daemon_unix_socket_fd > max)
|
||||||
@ -690,25 +792,25 @@ int main(int argc, char **argv)
|
|||||||
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
|
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
remote_domain_id = atoi(argv[optind]);
|
||||||
remote_domain_name = argv[optind+1];
|
remote_domain_name = argv[optind+1];
|
||||||
if (argc - optind >= 3)
|
if (argc - optind >= 3)
|
||||||
default_user = argv[optind+2];
|
default_user = argv[optind+2];
|
||||||
remote_domain_xid = atoi(argv[optind]);
|
init(remote_domain_id);
|
||||||
init(remote_domain_xid);
|
|
||||||
sigemptyset(&chld_set);
|
sigemptyset(&chld_set);
|
||||||
sigaddset(&chld_set, SIGCHLD);
|
sigaddset(&chld_set, SIGCHLD);
|
||||||
|
signal(SIGCHLD, sigchld_handler);
|
||||||
/*
|
/*
|
||||||
The main event loop. Waits for one of the following events:
|
* The main event loop. Waits for one of the following events:
|
||||||
- message from client
|
* - message from client
|
||||||
- message from agent
|
* - message from agent
|
||||||
- new client
|
* - new client
|
||||||
- child exited
|
* - child exited
|
||||||
*/
|
*/
|
||||||
for (;;) {
|
for (;;) {
|
||||||
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
|
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
|
||||||
if (libvchan_buffer_space(vchan) <=
|
if (libvchan_buffer_space(vchan) <= sizeof(struct msg_header))
|
||||||
sizeof(struct server_header))
|
FD_ZERO(&read_fdset); // vchan full - don't read from clients
|
||||||
FD_ZERO(&read_fdset); // vchan full - don't read from clients
|
|
||||||
|
|
||||||
sigprocmask(SIG_BLOCK, &chld_set, NULL);
|
sigprocmask(SIG_BLOCK, &chld_set, NULL);
|
||||||
if (child_exited)
|
if (child_exited)
|
||||||
@ -726,11 +828,7 @@ int main(int argc, char **argv)
|
|||||||
if (clients[i].state != CLIENT_INVALID
|
if (clients[i].state != CLIENT_INVALID
|
||||||
&& FD_ISSET(i, &read_fdset))
|
&& FD_ISSET(i, &read_fdset))
|
||||||
handle_message_from_client(i);
|
handle_message_from_client(i);
|
||||||
|
|
||||||
for (i = 0; i <= max_client_fd; i++)
|
|
||||||
if (clients[i].state != CLIENT_INVALID
|
|
||||||
&& FD_ISSET(i, &write_fdset))
|
|
||||||
write_buffered_data_to_client(i);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// vim:ts=4:sw=4:et:
|
||||||
|
@ -12,6 +12,7 @@ POLICY_FILE_DIR="/etc/qubes-rpc/policy"
|
|||||||
# XXX: Backward compatibility, to be removed soon
|
# XXX: Backward compatibility, to be removed soon
|
||||||
DEPRECATED_POLICY_FILE_DIR="/etc/qubes_rpc/policy"
|
DEPRECATED_POLICY_FILE_DIR="/etc/qubes_rpc/policy"
|
||||||
QREXEC_CLIENT="/usr/lib/qubes/qrexec-client"
|
QREXEC_CLIENT="/usr/lib/qubes/qrexec-client"
|
||||||
|
QUBES_RPC_MULTIPLEXER_PATH="/usr/lib/qubes/qubes-rpc-multiplexer"
|
||||||
|
|
||||||
class UserChoice:
|
class UserChoice:
|
||||||
ALLOW=0
|
ALLOW=0
|
||||||
@ -46,13 +47,13 @@ def line_to_dict(line):
|
|||||||
return dict
|
return dict
|
||||||
|
|
||||||
|
|
||||||
def read_policy_file(exec_index):
|
def read_policy_file(service_name):
|
||||||
policy_file=POLICY_FILE_DIR+"/"+exec_index
|
policy_file=POLICY_FILE_DIR+"/"+service_name
|
||||||
if not os.path.isfile(policy_file):
|
if not os.path.isfile(policy_file):
|
||||||
policy_file=DEPRECATED_POLICY_FILE_DIR+"/"+exec_index
|
policy_file=DEPRECATED_POLICY_FILE_DIR+"/"+service_name
|
||||||
if not os.path.isfile(policy_file):
|
if not os.path.isfile(policy_file):
|
||||||
return None
|
return None
|
||||||
print >>sys.stderr, "RPC service '%s' uses deprecated policy location, please move to %s" % (exec_index, POLICY_FILE_DIR)
|
print >>sys.stderr, "RPC service '%s' uses deprecated policy location, please move to %s" % (service_name, POLICY_FILE_DIR)
|
||||||
policy_list=list()
|
policy_list=list()
|
||||||
f = open(policy_file)
|
f = open(policy_file)
|
||||||
fcntl.flock(f, fcntl.LOCK_SH)
|
fcntl.flock(f, fcntl.LOCK_SH)
|
||||||
@ -82,6 +83,8 @@ def find_policy(policy, domain, target):
|
|||||||
return get_default_policy()
|
return get_default_policy()
|
||||||
|
|
||||||
def is_domain_running(target):
|
def is_domain_running(target):
|
||||||
|
if target == "dom0":
|
||||||
|
return True
|
||||||
libvirt_dom = vmm.libvirt_conn.lookupByName(target)
|
libvirt_dom = vmm.libvirt_conn.lookupByName(target)
|
||||||
if libvirt_dom:
|
if libvirt_dom:
|
||||||
return libvirt_dom.isActive()
|
return libvirt_dom.isActive()
|
||||||
@ -109,30 +112,33 @@ def spawn_target_if_necessary(target):
|
|||||||
subprocess.call(["qvm-run", "-a", "-q", target, "true"], stdin=null, stdout=null)
|
subprocess.call(["qvm-run", "-a", "-q", target, "true"], stdin=null, stdout=null)
|
||||||
null.close()
|
null.close()
|
||||||
|
|
||||||
def do_execute(domain, target, user, exec_index, process_ident):
|
def do_execute(domain, target, user, service_name, process_ident):
|
||||||
if target == "dom0":
|
if target == "$dispvm":
|
||||||
cmd="/usr/lib/qubes/qubes-rpc-multiplexer "+exec_index + " " + domain
|
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + service_name + " " + domain + " " +user
|
||||||
elif target == "$dispvm":
|
os.execl(QREXEC_CLIENT, "qrexec-client",
|
||||||
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + exec_index + " " + domain + " " +user
|
"-d", "dom0", "-c", process_ident, cmd)
|
||||||
else:
|
else:
|
||||||
# see the previous commit why "qvm-run -a" is broken and dangerous
|
# see the previous commit why "qvm-run -a" is broken and dangerous
|
||||||
# also, dangling "xl" would keep stderr open and may prevent closing connection
|
# also, dangling "xl" would keep stderr open and may prevent closing connection
|
||||||
spawn_target_if_necessary(target)
|
spawn_target_if_necessary(target)
|
||||||
cmd= QREXEC_CLIENT + " -d " + target + " '" + user
|
if target == "dom0":
|
||||||
cmd+=":QUBESRPC "+ exec_index + " " + domain + "'"
|
cmd = QUBES_RPC_MULTIPLEXER_PATH + " " + service_name + " " + domain
|
||||||
# stderr should be logged in source/target VM
|
else:
|
||||||
null = open(os.devnull, 'w')
|
cmd = user + ":QUBESRPC "+ service_name + " " + domain
|
||||||
os.dup2(null.fileno(), 2)
|
# stderr should be logged in source/target VM
|
||||||
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", cmd, "-c", process_ident)
|
null = open(os.devnull, 'w')
|
||||||
|
os.dup2(null.fileno(), 2)
|
||||||
|
os.execl(QREXEC_CLIENT, "qrexec-client",
|
||||||
|
"-d", target, "-c", process_ident, cmd)
|
||||||
|
|
||||||
def confirm_execution(domain, target, exec_index):
|
def confirm_execution(domain, target, service_name):
|
||||||
text = "Do you allow domain \"" +domain + "\" to execute " + exec_index
|
text = "Do you allow domain \"" +domain + "\" to execute " + service_name
|
||||||
text+= " operation on the domain \"" + target +"\"?<br>"
|
text+= " operation on the domain \"" + target +"\"?<br>"
|
||||||
text+= " \"Yes to All\" option will automatically allow this operation in the future."
|
text+= " \"Yes to All\" option will automatically allow this operation in the future."
|
||||||
return qubes.guihelpers.ask(text, yestoall=True)
|
return qubes.guihelpers.ask(text, yestoall=True)
|
||||||
|
|
||||||
def add_always_allow(domain, target, exec_index, options):
|
def add_always_allow(domain, target, service_name, options):
|
||||||
policy_file=POLICY_FILE_DIR+"/"+exec_index
|
policy_file=POLICY_FILE_DIR+"/"+service_name
|
||||||
if not os.path.isfile(policy_file):
|
if not os.path.isfile(policy_file):
|
||||||
return None
|
return None
|
||||||
f = open(policy_file, 'r+')
|
f = open(policy_file, 'r+')
|
||||||
@ -145,13 +151,13 @@ def add_always_allow(domain, target, exec_index, options):
|
|||||||
f.write("".join(lines))
|
f.write("".join(lines))
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def policy_editor(domain, target, exec_index):
|
def policy_editor(domain, target, service_name):
|
||||||
text = "No policy definition found for " + exec_index + " action. "
|
text = "No policy definition found for " + service_name + " action. "
|
||||||
text+= "Please create a policy file in Dom0 in " + POLICY_FILE_DIR + "/" + exec_index
|
text+= "Please create a policy file in Dom0 in " + POLICY_FILE_DIR + "/" + service_name
|
||||||
subprocess.call(["/usr/bin/zenity", "--info", "--text", text])
|
subprocess.call(["/usr/bin/zenity", "--info", "--text", text])
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
usage = "usage: %prog [options] <src-domain> <target-domain> <service> <process-ident>"
|
usage = "usage: %prog [options] <src-domain-id> <src-domain> <target-domain> <service> <process-ident>"
|
||||||
parser = OptionParser (usage)
|
parser = OptionParser (usage)
|
||||||
parser.add_option ("--assume-yes-for-ask", action="store_true", dest="assume_yes_for_ask", default=False,
|
parser.add_option ("--assume-yes-for-ask", action="store_true", dest="assume_yes_for_ask", default=False,
|
||||||
help="Allow run of service without confirmation if policy say 'ask'")
|
help="Allow run of service without confirmation if policy say 'ask'")
|
||||||
@ -159,22 +165,27 @@ def main():
|
|||||||
help="Do not run the service, only evaluate policy; retcode=0 means 'allow'")
|
help="Do not run the service, only evaluate policy; retcode=0 means 'allow'")
|
||||||
|
|
||||||
(options, args) = parser.parse_args ()
|
(options, args) = parser.parse_args ()
|
||||||
domain=args[0]
|
domain_id=args[0]
|
||||||
target=args[1]
|
domain=args[1]
|
||||||
exec_index=args[2]
|
target=args[2]
|
||||||
process_ident=args[3]
|
service_name=args[3]
|
||||||
|
process_ident=args[4]
|
||||||
|
|
||||||
|
# Add source domain information, required by qrexec-client for establishing
|
||||||
|
# connection
|
||||||
|
process_ident+=","+domain+","+domain_id
|
||||||
|
|
||||||
if not validate_target(target):
|
if not validate_target(target):
|
||||||
print >> sys.stderr, "Rpc failed (unknown domain):", domain, target, exec_index
|
print >> sys.stderr, "Rpc failed (unknown domain):", domain, target, service_name
|
||||||
text = "Domain '%s' doesn't exist (service %s called by domain %s)." % (
|
text = "Domain '%s' doesn't exist (service %s called by domain %s)." % (
|
||||||
target, exec_index, domain)
|
target, service_name, domain)
|
||||||
subprocess.call(["/usr/bin/zenity", "--error", "--text", text])
|
subprocess.call(["/usr/bin/zenity", "--error", "--text", text])
|
||||||
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
|
exit(1)
|
||||||
|
|
||||||
policy_list=read_policy_file(exec_index)
|
policy_list=read_policy_file(service_name)
|
||||||
if policy_list==None:
|
if policy_list==None:
|
||||||
policy_editor(domain, target, exec_index)
|
policy_editor(domain, target, service_name)
|
||||||
policy_list=read_policy_file(exec_index)
|
policy_list=read_policy_file(service_name)
|
||||||
if policy_list==None:
|
if policy_list==None:
|
||||||
policy_list=list()
|
policy_list=list()
|
||||||
|
|
||||||
@ -184,9 +195,9 @@ def main():
|
|||||||
policy_dict["action"] = "allow"
|
policy_dict["action"] = "allow"
|
||||||
|
|
||||||
if policy_dict["action"] == "ask":
|
if policy_dict["action"] == "ask":
|
||||||
user_choice = confirm_execution(domain, target, exec_index)
|
user_choice = confirm_execution(domain, target, service_name)
|
||||||
if user_choice == UserChoice.ALWAYS_ALLOW:
|
if user_choice == UserChoice.ALWAYS_ALLOW:
|
||||||
add_always_allow(domain, target, exec_index, policy_dict["full-action"].lstrip('ask'))
|
add_always_allow(domain, target, service_name, policy_dict["full-action"].lstrip('ask'))
|
||||||
policy_dict["action"] = "allow"
|
policy_dict["action"] = "allow"
|
||||||
elif user_choice == UserChoice.ALLOW:
|
elif user_choice == UserChoice.ALLOW:
|
||||||
policy_dict["action"] = "allow"
|
policy_dict["action"] = "allow"
|
||||||
@ -206,10 +217,10 @@ def main():
|
|||||||
user=policy_dict["action.user"]
|
user=policy_dict["action.user"]
|
||||||
else:
|
else:
|
||||||
user="DEFAULT"
|
user="DEFAULT"
|
||||||
print >> sys.stderr, "Rpc allowed:", domain, target, exec_index
|
print >> sys.stderr, "Rpc allowed:", domain, target, service_name
|
||||||
do_execute(domain, target, user, exec_index, process_ident)
|
do_execute(domain, target, user, service_name, process_ident)
|
||||||
|
|
||||||
print >> sys.stderr, "Rpc denied:", domain, target, exec_index
|
print >> sys.stderr, "Rpc denied:", domain, target, service_name
|
||||||
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
|
exit(1)
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
Loading…
Reference in New Issue
Block a user