qubes-core-admin-linux/qrexec/qrexec-daemon.c
Marek Marczykowski-Górecki 6b0fef6dba
qrexec: automatically translate '$' to '@' in target name specification
After the change of target keywords, apply transparen translation from
old names. This will avoid breaking calls from not updated VMs. Apply
this change only in stable branch.
2018-02-19 02:18:32 +01:00

858 lines
26 KiB
C

/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <assert.h>
#include "qrexec.h"
#include "libqrexec-utils.h"
enum client_state {
CLIENT_INVALID = 0, // table slot not used
CLIENT_HELLO, // waiting for client hello
CLIENT_CMDLINE, // waiting for cmdline from client
CLIENT_RUNNING // waiting for client termination (to release vchan port)
};
enum vchan_port_state {
VCHAN_PORT_UNUSED = -1
};
struct _client {
int state; // enum client_state
};
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.
Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity.
*/
#define MAX_CLIENTS MAX_FDS
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 qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor
const char *default_user = "user";
const char default_user_keyword[] = "DEFAULT:";
#define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2)
int opt_quiet = 0;
#ifdef __GNUC__
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
#else
# define UNUSED(x) UNUSED_ ## x
#endif
volatile int children_count;
libvchan_t *vchan;
void sigusr1_handler(int UNUSED(x))
{
if (!opt_quiet)
fprintf(stderr, "connected\n");
exit(0);
}
void sigchld_parent_handler(int UNUSED(x))
{
children_count--;
/* starting value is 0 so we see dead real qrexec-daemon as -1 */
if (children_count < 0) {
if (!opt_quiet)
fprintf(stderr, "failed\n");
else
fprintf(stderr, "Connection to the VM failed\n");
exit(1);
}
}
static void sigchld_handler(int UNUSED(x));
char *remote_domain_name; // guess what
int remote_domain_id;
void unlink_qrexec_socket()
{
char socket_address[40];
char link_to_socket_name[strlen(remote_domain_name) + sizeof(socket_address)];
snprintf(socket_address, sizeof(socket_address),
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", remote_domain_id);
snprintf(link_to_socket_name, sizeof link_to_socket_name,
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", remote_domain_name);
unlink(socket_address);
unlink(link_to_socket_name);
}
void handle_vchan_error(const char *op)
{
fprintf(stderr, "Error while vchan %s, exiting\n", op);
exit(1);
}
int create_qrexec_socket(int domid, const char *domname)
{
char socket_address[40];
char link_to_socket_name[strlen(domname) + sizeof(socket_address)];
snprintf(socket_address, sizeof(socket_address),
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid);
snprintf(link_to_socket_name, sizeof link_to_socket_name,
QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname);
unlink(link_to_socket_name);
if (symlink(socket_address, link_to_socket_name)) {
fprintf(stderr, "symlink(%s,%s) failed: %s\n", socket_address,
link_to_socket_name, strerror (errno));
}
atexit(unlink_qrexec_socket);
return get_server_socket(socket_address);
}
#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 */
void init(int xid)
{
char qrexec_error_log_name[256];
int logfd;
int i;
pid_t pid;
int startup_timeout = MAX_STARTUP_TIME_DEFAULT;
const char *startup_timeout_str = NULL;
if (xid <= 0) {
fprintf(stderr, "domain id=0?\n");
exit(1);
}
startup_timeout_str = getenv("QREXEC_STARTUP_TIMEOUT");
if (startup_timeout_str) {
startup_timeout = atoi(startup_timeout_str);
if (startup_timeout <= 0)
// invalid or negative number
startup_timeout = MAX_STARTUP_TIME_DEFAULT;
}
signal(SIGUSR1, sigusr1_handler);
signal(SIGCHLD, sigchld_parent_handler);
switch (pid=fork()) {
case -1:
perror("fork");
exit(1);
case 0:
break;
default:
if (getenv("QREXEC_STARTUP_NOWAIT"))
exit(0);
if (!opt_quiet)
fprintf(stderr, "Waiting for VM's qrexec agent.");
for (i=0;i<startup_timeout;i++) {
sleep(1);
if (!opt_quiet)
fprintf(stderr, ".");
if (i==startup_timeout-1) {
break;
}
}
fprintf(stderr, "Cannot connect to '%s' qrexec agent for %d seconds, giving up\n", remote_domain_name, startup_timeout);
exit(1);
}
close(0);
snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name),
"/var/log/qubes/qrexec.%s.log", remote_domain_name);
umask(0007); // make the log readable by the "qubes" group
logfd =
open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC,
0660);
if (logfd < 0) {
perror("open");
exit(1);
}
dup2(logfd, 1);
dup2(logfd, 2);
chdir("/var/run/qubes");
if (setsid() < 0) {
perror("setsid()");
exit(1);
}
vchan = libvchan_client_init(xid, VCHAN_BASE_PORT);
if (!vchan) {
perror("cannot connect to qrexec agent");
exit(1);
}
if (handle_agent_hello(vchan, remote_domain_name) < 0) {
exit(1);
}
if (setgid(getgid()) < 0) {
perror("setgid()");
exit(1);
}
if (setuid(getuid()) < 0) {
perror("setuid()");
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 */
umask(0);
qrexec_daemon_unix_socket_fd =
create_qrexec_socket(xid, remote_domain_name);
umask(0077);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, sigchld_handler);
signal(SIGUSR1, SIG_DFL);
kill(getppid(), SIGUSR1); // let the parent know we are ready
}
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);
if (fd >= MAX_CLIENTS) {
fprintf(stderr, "too many clients ?\n");
exit(1);
}
if (send_client_hello(fd) < 0) {
close(fd);
clients[fd].state = CLIENT_INVALID;
return;
}
clients[fd].state = CLIENT_HELLO;
if (fd > max_client_fd)
max_client_fd = fd;
}
static void terminate_client(int fd)
{
clients[fd].state = CLIENT_INVALID;
close(fd);
}
static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr)
{
struct exec_params params;
int len = hdr->len-sizeof(params);
char buf[len];
int use_default_user = 0;
int i;
if (!read_all(fd, &params, sizeof(params))) {
terminate_client(fd);
return 0;
}
if (!read_all(fd, buf, len)) {
terminate_client(fd);
return 0;
}
if (hdr->type == MSG_SERVICE_CONNECT) {
/* if the service was accepted, do not send spurious
* MSG_SERVICE_REFUSED when service process itself exit with non-zero
* code */
for (i = 0; i <= policy_pending_max; i++) {
if (policy_pending[i].pid &&
strncmp(policy_pending[i].params.ident, buf, len) == 0) {
policy_pending[i].pid = 0;
while (policy_pending_max > 0 &&
policy_pending[policy_pending_max].pid == 0)
policy_pending_max--;
break;
}
}
}
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)) {
use_default_user = 1;
hdr->len -= default_user_keyword_len_without_colon;
hdr->len += strlen(default_user);
}
if (libvchan_send(vchan, hdr, sizeof(*hdr)) < 0)
handle_vchan_error("send");
if (libvchan_send(vchan, &params, sizeof(params)) < 0)
handle_vchan_error("send params");
if (use_default_user) {
if (libvchan_send(vchan, default_user, strlen(default_user)) < 0)
handle_vchan_error("send default_user");
if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon,
len-default_user_keyword_len_without_colon) < 0)
handle_vchan_error("send buf");
} else
if (libvchan_send(vchan, buf, len) < 0)
handle_vchan_error("send buf");
return 1;
}
static void handle_cmdline_message_from_client(int fd)
{
struct msg_header hdr;
if (!read_all(fd, &hdr, sizeof hdr)) {
terminate_client(fd);
return;
}
switch (hdr.type) {
case MSG_EXEC_CMDLINE:
case MSG_JUST_EXEC:
case MSG_SERVICE_CONNECT:
break;
default:
terminate_client(fd);
return;
}
if (!handle_cmdline_body_from_client(fd, &hdr))
// client disconnected while sending cmdline, above call already
// cleaned up client info
return;
clients[fd].state = CLIENT_RUNNING;
}
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 */
static void handle_message_from_client(int fd)
{
char buf[MAX_DATA_CHUNK];
switch (clients[fd].state) {
case CLIENT_HELLO:
handle_client_hello(fd);
return;
case CLIENT_CMDLINE:
handle_cmdline_message_from_client(fd);
return;
case CLIENT_RUNNING:
// expected EOF
if (read(fd, buf, sizeof(buf)) != 0) {
fprintf(stderr, "Unexpected data received from client %d\n", fd);
}
terminate_client(fd);
return;
default:
fprintf(stderr, "Invalid client state %d\n", clients[fd].state);
exit(1);
}
}
/*
* The signal handler executes asynchronously; therefore all it should do is
* to set a flag "signal has arrived", and let the main even loop react to this
* flag in appropriate moment.
*/
int child_exited;
static void sigchld_handler(int UNUSED(x))
{
child_exited = 1;
signal(SIGCHLD, sigchld_handler);
}
static void send_service_refused(libvchan_t *vchan, struct service_params *params) {
struct msg_header hdr;
hdr.type = MSG_SERVICE_REFUSED;
hdr.len = sizeof(*params);
if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED hdr to agent\n");
exit(1);
}
if (libvchan_send(vchan, params, sizeof(*params)) != sizeof(*params)) {
fprintf(stderr, "Failed to send MSG_SERVICE_REFUSED to agent\n");
exit(1);
}
}
/* 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) {
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, char *extra_allowed_chars)
{
unsigned char * untrusted_s;
for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) {
if (*untrusted_s >= 'a' && *untrusted_s <= 'z')
continue;
if (*untrusted_s >= 'A' && *untrusted_s <= 'Z')
continue;
if (*untrusted_s >= '0' && *untrusted_s <= '9')
continue;
if (*untrusted_s == '$' && strchr(extra_allowed_chars, '@')) {
*untrusted_s = '@';
continue;
}
if (*untrusted_s == '_' ||
*untrusted_s == '-' ||
*untrusted_s == '.')
continue;
if (extra_allowed_chars && strchr(extra_allowed_chars, *untrusted_s))
continue;
*untrusted_s = '_';
}
}
#define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0
/*
* Called when agent sends a message asking to execute a predefined command.
*/
static void handle_execute_service(void)
{
int i;
int policy_pending_slot;
pid_t pid;
struct trigger_service_params untrusted_params, params;
char remote_domain_id_str[10];
if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) < 0)
handle_vchan_error("recv params");
/* sanitize start */
ENSURE_NULL_TERMINATED(untrusted_params.service_name);
ENSURE_NULL_TERMINATED(untrusted_params.target_domain);
ENSURE_NULL_TERMINATED(untrusted_params.request_id.ident);
sanitize_name(untrusted_params.service_name, "+");
sanitize_name(untrusted_params.target_domain, "@");
sanitize_name(untrusted_params.request_id.ident, " ");
params = untrusted_params;
/* sanitize end */
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:
perror("fork");
exit(1);
case 0:
break;
default:
policy_pending[policy_pending_slot].pid = pid;
policy_pending[policy_pending_slot].params = untrusted_params.request_id;
return;
}
for (i = 3; i < MAX_FDS; i++)
close(i);
signal(SIGCHLD, 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", "--",
remote_domain_id_str, remote_domain_name, params.target_domain,
params.service_name, params.request_id.ident, NULL);
perror("execl");
_exit(1);
}
static void handle_connection_terminated()
{
struct exec_params untrusted_params, params;
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);
}
/* 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)
{
switch (untrusted_header->type) {
case MSG_TRIGGER_SERVICE:
if (untrusted_header->len != sizeof(struct trigger_service_params)) {
fprintf(stderr, "agent sent invalid MSG_TRIGGER_SERVICE packet\n");
exit(1);
}
break;
case MSG_CONNECTION_TERMINATED:
if (untrusted_header->len != sizeof(struct exec_params)) {
fprintf(stderr, "agent sent invalid MSG_CONNECTION_TERMINATED packet\n");
exit(1);
}
break;
default:
fprintf(stderr, "unknown mesage type 0x%x from agent\n",
untrusted_header->type);
exit(1);
}
}
static void handle_message_from_agent(void)
{
struct msg_header hdr, untrusted_hdr;
if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) < 0)
handle_vchan_error("recv hdr");
/* sanitize start */
sanitize_message_from_agent(&untrusted_hdr);
hdr = untrusted_hdr;
/* sanitize end */
// fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id,
// hdr.len);
switch (hdr.type) {
case MSG_TRIGGER_SERVICE:
handle_execute_service();
return;
case MSG_CONNECTION_TERMINATED:
handle_connection_terminated();
return;
}
}
/*
* Scan the "clients" table, add ones we want to read from (because the other
* end has not send MSG_XOFF on them) to read_fdset, add ones we want to write
* to (because its pipe is full) to write_fdset. Return the highest used file
* descriptor number, needed for the first select() parameter.
*/
static int fill_fdsets_for_select(fd_set * read_fdset, fd_set * write_fdset)
{
int i;
int max = -1;
FD_ZERO(read_fdset);
FD_ZERO(write_fdset);
for (i = 0; i <= max_client_fd; i++) {
if (clients[i].state != CLIENT_INVALID) {
FD_SET(i, read_fdset);
max = i;
}
}
FD_SET(qrexec_daemon_unix_socket_fd, read_fdset);
if (qrexec_daemon_unix_socket_fd > max)
max = qrexec_daemon_unix_socket_fd;
return max;
}
int main(int argc, char **argv)
{
fd_set read_fdset, write_fdset;
int i, opt;
int max;
sigset_t chld_set;
while ((opt=getopt(argc, argv, "q")) != -1) {
switch (opt) {
case 'q':
opt_quiet = 1;
break;
default: /* '?' */
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
exit(1);
}
}
if (argc - optind < 2 || argc - optind > 3) {
fprintf(stderr, "usage: %s [-q] domainid domain-name [default user]\n", argv[0]);
exit(1);
}
remote_domain_id = atoi(argv[optind]);
remote_domain_name = argv[optind+1];
if (argc - optind >= 3)
default_user = argv[optind+2];
init(remote_domain_id);
sigemptyset(&chld_set);
sigaddset(&chld_set, SIGCHLD);
signal(SIGCHLD, sigchld_handler);
/*
* The main event loop. Waits for one of the following events:
* - message from client
* - message from agent
* - new client
* - child exited
*/
for (;;) {
max = fill_fdsets_for_select(&read_fdset, &write_fdset);
if (libvchan_buffer_space(vchan) <= (int)sizeof(struct msg_header))
FD_ZERO(&read_fdset); // vchan full - don't read from clients
sigprocmask(SIG_BLOCK, &chld_set, NULL);
if (child_exited)
reap_children();
wait_for_vchan_or_argfd(vchan, max, &read_fdset, &write_fdset);
sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
if (FD_ISSET(qrexec_daemon_unix_socket_fd, &read_fdset))
handle_new_client();
while (libvchan_data_ready(vchan))
handle_message_from_agent();
for (i = 0; i <= max_client_fd; i++)
if (clients[i].state != CLIENT_INVALID
&& FD_ISSET(i, &read_fdset))
handle_message_from_client(i);
}
}
// vim:ts=4:sw=4:et: