2013-03-20 05:24:17 +00:00
|
|
|
/*
|
|
|
|
* 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 "qrexec.h"
|
|
|
|
#include "libqrexec-utils.h"
|
|
|
|
|
|
|
|
enum client_flags {
|
|
|
|
CLIENT_INVALID = 0, // table slot not used
|
|
|
|
CLIENT_CMDLINE = 1, // waiting for cmdline from client
|
|
|
|
CLIENT_DATA = 2, // waiting for data 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_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
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _client {
|
|
|
|
int state; // combination of above enum client_flags
|
|
|
|
struct buffer buffer; // buffered data to client, if any
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
2014-01-12 11:41:11 +00:00
|
|
|
const char *default_user = "user";
|
|
|
|
const char default_user_keyword[] = "DEFAULT:";
|
2013-03-20 05:24:17 +00:00
|
|
|
#define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2)
|
|
|
|
|
2014-10-24 20:03:46 +00:00
|
|
|
int opt_quiet = 0;
|
|
|
|
|
2014-02-16 09:29:06 +00:00
|
|
|
#ifdef __GNUC__
|
|
|
|
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
|
|
|
|
#else
|
|
|
|
# define UNUSED(x) UNUSED_ ## x
|
|
|
|
#endif
|
|
|
|
|
2013-12-27 19:31:19 +00:00
|
|
|
volatile int children_count;
|
2013-10-27 15:03:33 +00:00
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
libvchan_t *vchan;
|
|
|
|
|
2014-02-16 09:29:06 +00:00
|
|
|
void sigusr1_handler(int UNUSED(x))
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
2014-10-24 20:03:46 +00:00
|
|
|
if (!opt_quiet)
|
|
|
|
fprintf(stderr, "connected\n");
|
2013-03-20 05:24:17 +00:00
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2014-02-16 09:29:06 +00:00
|
|
|
void sigchld_parent_handler(int UNUSED(x))
|
2013-10-27 15:03:33 +00:00
|
|
|
{
|
|
|
|
children_count--;
|
|
|
|
/* starting value is 0 so we see dead real qrexec-daemon as -1 */
|
|
|
|
if (children_count < 0) {
|
2014-10-24 20:03:46 +00:00
|
|
|
if (!opt_quiet)
|
|
|
|
fprintf(stderr, "failed\n");
|
|
|
|
else
|
|
|
|
fprintf(stderr, "Connection to the VM failed\n");
|
2013-10-27 15:03:33 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
void sigchld_handler(int x);
|
|
|
|
|
2014-01-12 11:41:11 +00:00
|
|
|
const char *remote_domain_name; // guess what
|
2014-02-04 22:27:04 +00:00
|
|
|
int remote_domain_xid; // guess what
|
|
|
|
|
|
|
|
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_xid);
|
|
|
|
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);
|
|
|
|
}
|
2013-03-20 05:24:17 +00:00
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
void handle_vchan_error(const char *op) {
|
|
|
|
fprintf(stderr, "Error while vchan %s, exiting\n", op);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-12-30 11:36:36 +00:00
|
|
|
int create_qrexec_socket(int domid, const char *domname)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
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);
|
2014-01-12 11:41:27 +00:00
|
|
|
if (symlink(socket_address, link_to_socket_name)) {
|
|
|
|
fprintf(stderr, "symlink(%s,%s) failed: %s\n", socket_address,
|
|
|
|
link_to_socket_name, strerror (errno));
|
|
|
|
}
|
2014-02-04 22:27:04 +00:00
|
|
|
atexit(unlink_qrexec_socket);
|
2013-03-20 05:24:17 +00:00
|
|
|
return get_server_socket(socket_address);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_STARTUP_TIME_DEFAULT 60
|
|
|
|
|
|
|
|
/* 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;
|
2014-01-12 11:41:11 +00:00
|
|
|
const char *startup_timeout_str = NULL;
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
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);
|
2013-12-27 19:32:33 +00:00
|
|
|
if (startup_timeout <= 0)
|
|
|
|
// invalid or negative number
|
2013-03-20 05:24:17 +00:00
|
|
|
startup_timeout = MAX_STARTUP_TIME_DEFAULT;
|
|
|
|
}
|
|
|
|
signal(SIGUSR1, sigusr1_handler);
|
2013-10-27 15:03:33 +00:00
|
|
|
signal(SIGCHLD, sigchld_parent_handler);
|
2013-03-20 05:24:17 +00:00
|
|
|
switch (pid=fork()) {
|
|
|
|
case -1:
|
|
|
|
perror("fork");
|
|
|
|
exit(1);
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
default:
|
2013-10-23 03:43:37 +00:00
|
|
|
if (getenv("QREXEC_STARTUP_NOWAIT"))
|
|
|
|
exit(0);
|
2014-10-24 20:03:46 +00:00
|
|
|
if (!opt_quiet)
|
|
|
|
fprintf(stderr, "Waiting for VM's qrexec agent.");
|
2013-03-20 05:24:17 +00:00
|
|
|
for (i=0;i<startup_timeout;i++) {
|
|
|
|
sleep(1);
|
2014-10-24 20:03:46 +00:00
|
|
|
if (!opt_quiet)
|
|
|
|
fprintf(stderr, ".");
|
2013-03-20 05:24:17 +00:00
|
|
|
if (i==startup_timeout-1) {
|
2014-02-05 02:31:47 +00:00
|
|
|
break;
|
2013-03-20 05:24:17 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-22 03:16:26 +00:00
|
|
|
fprintf(stderr, "Cannot connect to '%s' qrexec agent for %d seconds, giving up\n", remote_domain_name, startup_timeout);
|
2013-03-20 05:24:17 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
close(0);
|
|
|
|
snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name),
|
2013-10-18 00:27:17 +00:00
|
|
|
"/var/log/qubes/qrexec.%s.log", remote_domain_name);
|
2013-03-20 05:24:17 +00:00
|
|
|
umask(0007); // make the log readable by the "qubes" group
|
|
|
|
logfd =
|
|
|
|
open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC,
|
|
|
|
0640);
|
|
|
|
|
|
|
|
if (logfd < 0) {
|
|
|
|
perror("open");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
dup2(logfd, 1);
|
|
|
|
dup2(logfd, 2);
|
|
|
|
|
|
|
|
chdir("/var/run/qubes");
|
|
|
|
if (setsid() < 0) {
|
|
|
|
perror("setsid()");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
vchan = libvchan_client_init(xid, REXEC_PORT);
|
2013-05-04 02:18:30 +00:00
|
|
|
if (!vchan) {
|
|
|
|
perror("cannot connect to qrexec agent");
|
|
|
|
exit(1);
|
|
|
|
}
|
2013-04-22 03:16:10 +00:00
|
|
|
/* wait for connection */
|
|
|
|
while (!libvchan_is_open(vchan))
|
|
|
|
libvchan_wait(vchan);
|
2013-12-27 19:34:57 +00:00
|
|
|
if (setgid(getgid()) < 0) {
|
|
|
|
perror("setgid()");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (setuid(getuid()) < 0) {
|
|
|
|
perror("setuid()");
|
|
|
|
exit(1);
|
|
|
|
}
|
2013-03-20 05:24:17 +00:00
|
|
|
/* 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
|
|
|
|
}
|
|
|
|
|
2013-12-30 11:36:36 +00:00
|
|
|
void handle_new_client(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
int fd = do_accept(qrexec_daemon_unix_socket_fd);
|
|
|
|
if (fd >= MAX_CLIENTS) {
|
|
|
|
fprintf(stderr, "too many clients ?\n");
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
clients[fd].state = CLIENT_CMDLINE;
|
|
|
|
buffer_init(&clients[fd].buffer);
|
|
|
|
if (fd > max_client_fd)
|
|
|
|
max_client_fd = fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
void terminate_client_and_flush_data(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;
|
|
|
|
buffer_free(&clients[fd].buffer);
|
|
|
|
if (max_client_fd == fd) {
|
2013-12-27 19:35:55 +00:00
|
|
|
for (i = fd; i >= 0 && clients[i].state == CLIENT_INVALID;
|
2013-03-20 05:24:17 +00:00
|
|
|
i--);
|
|
|
|
max_client_fd = i;
|
|
|
|
}
|
|
|
|
s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END;
|
|
|
|
s_hdr.client_id = fd;
|
|
|
|
s_hdr.len = 0;
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_send(vchan, &s_hdr, sizeof(s_hdr)) < 0)
|
|
|
|
handle_vchan_error("send");
|
2013-03-20 05:24:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header
|
|
|
|
*s_hdr)
|
|
|
|
{
|
|
|
|
int len = s_hdr->len;
|
|
|
|
char buf[len];
|
|
|
|
int use_default_user = 0;
|
|
|
|
if (!read_all(fd, buf, len)) {
|
|
|
|
terminate_client_and_flush_data(fd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1)) {
|
|
|
|
use_default_user = 1;
|
|
|
|
s_hdr->len -= default_user_keyword_len_without_colon; // -1 because of colon
|
|
|
|
s_hdr->len += strlen(default_user);
|
|
|
|
}
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_send(vchan, s_hdr, sizeof(*s_hdr)) < 0)
|
|
|
|
handle_vchan_error("send");
|
2013-03-20 05:24:17 +00:00
|
|
|
if (use_default_user) {
|
2013-04-22 03:16:10 +00:00
|
|
|
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");
|
2013-03-20 05:24:17 +00:00
|
|
|
} else
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_send(vchan, buf, len) < 0)
|
|
|
|
handle_vchan_error("send buf");
|
2013-03-20 05:24:17 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void handle_cmdline_message_from_client(int fd)
|
|
|
|
{
|
|
|
|
struct client_header hdr;
|
|
|
|
struct server_header s_hdr;
|
|
|
|
if (!read_all(fd, &hdr, sizeof hdr)) {
|
|
|
|
terminate_client_and_flush_data(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (hdr.type) {
|
|
|
|
case MSG_CLIENT_TO_SERVER_EXEC_CMDLINE:
|
|
|
|
s_hdr.type = MSG_SERVER_TO_AGENT_EXEC_CMDLINE;
|
|
|
|
break;
|
|
|
|
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;
|
|
|
|
default:
|
|
|
|
terminate_client_and_flush_data(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_hdr.client_id = fd;
|
|
|
|
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
|
|
|
|
// cleaned up client info
|
|
|
|
return;
|
|
|
|
clients[fd].state = CLIENT_DATA;
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* handle data received from one of qrexec_client processes */
|
|
|
|
void handle_message_from_client(int fd)
|
|
|
|
{
|
|
|
|
struct server_header s_hdr;
|
|
|
|
char buf[MAX_DATA_CHUNK];
|
2014-02-19 19:54:39 +00:00
|
|
|
unsigned int len;
|
|
|
|
int ret;
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
if (clients[fd].state == CLIENT_CMDLINE) {
|
|
|
|
handle_cmdline_message_from_client(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// We have already passed cmdline from client.
|
|
|
|
// Now the client passes us raw data from its stdin.
|
2013-04-22 03:16:10 +00:00
|
|
|
len = libvchan_buffer_space(vchan);
|
2014-02-19 19:54:39 +00:00
|
|
|
if (len <= sizeof s_hdr)
|
2013-03-20 05:24:17 +00:00
|
|
|
return;
|
|
|
|
/* Read at most the amount of data that we have room for in vchan */
|
|
|
|
ret = read(fd, buf, len - sizeof(s_hdr));
|
|
|
|
if (ret < 0) {
|
|
|
|
perror("read client");
|
|
|
|
terminate_client_and_flush_data(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
s_hdr.client_id = fd;
|
|
|
|
s_hdr.len = ret;
|
|
|
|
s_hdr.type = MSG_SERVER_TO_AGENT_INPUT;
|
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
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");
|
2013-03-20 05:24:17 +00:00
|
|
|
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
|
2013-04-22 03:16:10 +00:00
|
|
|
(vchan, client_id, client_id, &clients[client_id].buffer)) {
|
2013-03-20 05:24:17 +00:00
|
|
|
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:
|
|
|
|
fprintf(stderr, "unknown flush_client_data?\n");
|
|
|
|
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 */
|
2014-02-15 12:14:23 +00:00
|
|
|
memcpy(buf, hdr, sizeof(*hdr));
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_recv(vchan, buf + sizeof(*hdr), len) < 0)
|
|
|
|
handle_vchan_error("recv buf");
|
2013-03-20 05:24:17 +00:00
|
|
|
if (clients[client_id].state & CLIENT_EXITED)
|
|
|
|
// ignore data for no longer running client
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (write_stdin
|
2013-04-22 03:16:10 +00:00
|
|
|
(vchan, client_id, client_id, buf, len + sizeof(*hdr),
|
2013-03-20 05:24:17 +00:00
|
|
|
&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
|
|
|
|
to set a flag "signal has arrived", and let the main even loop react to this
|
|
|
|
flag in appropriate moment.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int child_exited;
|
|
|
|
|
2014-02-16 09:29:06 +00:00
|
|
|
void sigchld_handler(int UNUSED(x))
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
child_exited = 1;
|
|
|
|
signal(SIGCHLD, sigchld_handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clean zombies, update children_count */
|
2013-12-30 11:36:36 +00:00
|
|
|
void reap_children(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
while (waitpid(-1, &status, WNOHANG) > 0)
|
|
|
|
children_count--;
|
|
|
|
child_exited = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* too many children - wait for one of them to terminate */
|
2013-12-30 11:36:36 +00:00
|
|
|
void wait_for_child(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
int status;
|
|
|
|
waitpid(-1, &status, 0);
|
|
|
|
children_count--;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_CHILDREN 10
|
2013-12-30 11:36:36 +00:00
|
|
|
void check_children_count_and_wait_if_too_many(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
if (children_count > MAX_CHILDREN) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"max number of children reached, waiting for child exit...\n");
|
|
|
|
wait_for_child();
|
|
|
|
fprintf(stderr, "now children_count=%d, continuing.\n",
|
|
|
|
children_count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sanitize_name(char * untrusted_s_signed)
|
|
|
|
{
|
|
|
|
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 == '$' || *untrusted_s == '_' || *untrusted_s == '-' || *untrusted_s == '.' || *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.
|
|
|
|
*/
|
|
|
|
|
2013-12-30 11:36:36 +00:00
|
|
|
void handle_execute_predefined_command(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct trigger_connect_params untrusted_params, params;
|
|
|
|
|
|
|
|
check_children_count_and_wait_if_too_many();
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_recv(vchan, &untrusted_params, sizeof(params)) < 0)
|
|
|
|
handle_vchan_error("recv params");
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
/* sanitize start */
|
|
|
|
ENSURE_NULL_TERMINATED(untrusted_params.exec_index);
|
|
|
|
ENSURE_NULL_TERMINATED(untrusted_params.target_vmname);
|
|
|
|
ENSURE_NULL_TERMINATED(untrusted_params.process_fds.ident);
|
|
|
|
sanitize_name(untrusted_params.exec_index);
|
|
|
|
sanitize_name(untrusted_params.target_vmname);
|
|
|
|
sanitize_name(untrusted_params.process_fds.ident);
|
|
|
|
params = untrusted_params;
|
|
|
|
/* sanitize end */
|
|
|
|
|
|
|
|
switch (fork()) {
|
|
|
|
case -1:
|
|
|
|
perror("fork");
|
|
|
|
exit(1);
|
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
children_count++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (i = 3; i < MAX_FDS; i++)
|
|
|
|
close(i);
|
|
|
|
signal(SIGCHLD, SIG_DFL);
|
|
|
|
signal(SIGPIPE, SIG_DFL);
|
|
|
|
execl("/usr/lib/qubes/qrexec-policy", "qrexec-policy",
|
|
|
|
remote_domain_name, params.target_vmname,
|
|
|
|
params.exec_index, params.process_fds.ident, NULL);
|
|
|
|
perror("execl");
|
2014-02-04 22:27:04 +00:00
|
|
|
_exit(1);
|
2013-03-20 05:24:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void check_client_id_in_range(unsigned int untrusted_client_id)
|
|
|
|
{
|
2014-02-16 09:29:06 +00:00
|
|
|
if (untrusted_client_id >= MAX_CLIENTS) {
|
2013-03-20 05:24:17 +00:00
|
|
|
fprintf(stderr, "from agent: client_id=%d\n",
|
|
|
|
untrusted_client_id);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void sanitize_message_from_agent(struct server_header *untrusted_header)
|
|
|
|
{
|
|
|
|
switch (untrusted_header->type) {
|
|
|
|
case MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING:
|
|
|
|
break;
|
|
|
|
case MSG_AGENT_TO_SERVER_STDOUT:
|
|
|
|
case MSG_AGENT_TO_SERVER_STDERR:
|
|
|
|
case MSG_AGENT_TO_SERVER_EXIT_CODE:
|
|
|
|
check_client_id_in_range(untrusted_header->client_id);
|
2014-02-16 09:29:06 +00:00
|
|
|
if (untrusted_header->len > MAX_DATA_CHUNK) {
|
2013-03-20 05:24:17 +00:00
|
|
|
fprintf(stderr, "agent feeded %d of data bytes?\n",
|
|
|
|
untrusted_header->len);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_XOFF:
|
|
|
|
case MSG_XON:
|
|
|
|
check_client_id_in_range(untrusted_header->client_id);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "unknown mesage type %d from agent\n",
|
|
|
|
untrusted_header->type);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-30 11:36:36 +00:00
|
|
|
void handle_message_from_agent(void)
|
2013-03-20 05:24:17 +00:00
|
|
|
{
|
|
|
|
struct client_header hdr;
|
|
|
|
struct server_header s_hdr, untrusted_s_hdr;
|
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_recv(vchan, &untrusted_s_hdr, sizeof(untrusted_s_hdr)) < 0)
|
|
|
|
handle_vchan_error("recv hdr");
|
2013-03-20 05:24:17 +00:00
|
|
|
/* sanitize start */
|
|
|
|
sanitize_message_from_agent(&untrusted_s_hdr);
|
|
|
|
s_hdr = untrusted_s_hdr;
|
|
|
|
/* sanitize end */
|
|
|
|
|
|
|
|
// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.client_id,
|
|
|
|
// s_hdr.len);
|
|
|
|
|
|
|
|
if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING) {
|
|
|
|
handle_execute_predefined_command();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_hdr.type == MSG_XOFF) {
|
|
|
|
clients[s_hdr.client_id].state |= CLIENT_DONT_READ;
|
|
|
|
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];
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_recv(vchan, buf, s_hdr.len) < 0)
|
|
|
|
handle_vchan_error("recv buf");
|
2013-03-20 05:24:17 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
&& !(clients[i].state & CLIENT_DONT_READ)) {
|
|
|
|
FD_SET(i, read_fdset);
|
|
|
|
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);
|
|
|
|
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;
|
2014-10-24 20:03:46 +00:00
|
|
|
int i, opt;
|
2013-03-20 05:24:17 +00:00
|
|
|
int max;
|
|
|
|
sigset_t chld_set;
|
|
|
|
|
2014-10-24 20:03:46 +00:00
|
|
|
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]);
|
2013-03-20 05:24:17 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2014-10-24 20:03:46 +00:00
|
|
|
remote_domain_name = argv[optind+1];
|
|
|
|
if (argc - optind >= 3)
|
|
|
|
default_user = argv[optind+2];
|
|
|
|
remote_domain_xid = atoi(argv[optind]);
|
2014-02-04 22:27:04 +00:00
|
|
|
init(remote_domain_xid);
|
2013-03-20 05:24:17 +00:00
|
|
|
sigemptyset(&chld_set);
|
|
|
|
sigaddset(&chld_set, SIGCHLD);
|
|
|
|
/*
|
|
|
|
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);
|
2013-04-22 03:16:10 +00:00
|
|
|
if (libvchan_buffer_space(vchan) <=
|
2014-02-19 19:54:39 +00:00
|
|
|
sizeof(struct server_header))
|
2013-03-20 05:24:17 +00:00
|
|
|
FD_ZERO(&read_fdset); // vchan full - don't read from clients
|
|
|
|
|
|
|
|
sigprocmask(SIG_BLOCK, &chld_set, NULL);
|
|
|
|
if (child_exited)
|
|
|
|
reap_children();
|
2013-04-22 03:16:10 +00:00
|
|
|
wait_for_vchan_or_argfd(vchan, max, &read_fdset, &write_fdset);
|
2013-03-20 05:24:17 +00:00
|
|
|
sigprocmask(SIG_UNBLOCK, &chld_set, NULL);
|
|
|
|
|
|
|
|
if (FD_ISSET(qrexec_daemon_unix_socket_fd, &read_fdset))
|
|
|
|
handle_new_client();
|
|
|
|
|
2013-04-22 03:16:10 +00:00
|
|
|
while (libvchan_data_ready(vchan))
|
2013-03-20 05:24:17 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|