Merge branch 'qrexec2' of git://git.qubes-os.org/rafal/core
This commit is contained in:
commit
371fdf5884
@ -1,9 +1,9 @@
|
||||
CC=gcc
|
||||
CFLAGS=-g -Wall -I../common -fPIC -pie
|
||||
all: dvm_file_editor qfile-agent-dvm qfile-agent qfile-unpacker
|
||||
dvm_file_editor: dvm_file_editor.o ../common/ioall.o
|
||||
all: vm-file-editor qopen-in-vm qfile-agent qfile-unpacker
|
||||
vm-file-editor: vm-file-editor.o ../common/ioall.o
|
||||
$(CC) -pie -g -o $@ $^
|
||||
qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o
|
||||
qopen-in-vm: qopen-in-vm.o ../common/ioall.o ../common/gui-fatal.o
|
||||
$(CC) -pie -g -o $@ $^
|
||||
qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o ../common/copy_file.o ../common/crc32.o
|
||||
$(CC) -pie -g -o $@ $^
|
||||
@ -11,4 +11,4 @@ qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o ../comm
|
||||
$(CC) -pie -g -o $@ $^
|
||||
|
||||
clean:
|
||||
rm -f qfile-agent-dvm qfile-agent qfile-unpacker dvm_file_editor *.o *~
|
||||
rm -f qopen-in-vm qfile-agent qfile-unpacker vm-file-editor *.o *~
|
||||
|
@ -20,24 +20,33 @@ enum {
|
||||
};
|
||||
|
||||
unsigned long crc32_sum;
|
||||
int write_all_with_crc(int fd, void *buf, int size) {
|
||||
int write_all_with_crc(int fd, void *buf, int size)
|
||||
{
|
||||
crc32_sum = Crc32_ComputeBuf(crc32_sum, buf, size);
|
||||
return write_all(fd, buf, size);
|
||||
}
|
||||
|
||||
|
||||
char *client_flags;
|
||||
void do_notify_progress(long long total, int flag)
|
||||
{
|
||||
FILE *progress;
|
||||
if (!client_flags[0])
|
||||
char *du_size_env = getenv("FILECOPY_TOTAL_SIZE");
|
||||
char *progress_type_env = getenv("PROGRESS_TYPE");
|
||||
char *saved_stdout_env = getenv("SAVED_FD_1");
|
||||
if (!progress_type_env)
|
||||
return;
|
||||
progress = fopen(client_flags, "w");
|
||||
if (!progress)
|
||||
return;
|
||||
fprintf(progress, "%d %lld %s", getpid(), total,
|
||||
flag == PROGRESS_FLAG_DONE ? "DONE" : "BUSY");
|
||||
fclose(progress);
|
||||
if (!strcmp(progress_type_env, "console") && du_size_env) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "sent %lld/%lld KB\r",
|
||||
total / 1024, strtoull(du_size_env, NULL, 0));
|
||||
write(2, msg, strlen(msg));
|
||||
if (flag == PROGRESS_FLAG_DONE)
|
||||
write(2, "\n", 1);
|
||||
}
|
||||
if (!strcmp(progress_type_env, "gui") && saved_stdout_env) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "%lld\n", total);
|
||||
write(strtoul(saved_stdout_env, NULL, 0), msg,
|
||||
strlen(msg));
|
||||
}
|
||||
}
|
||||
|
||||
void notify_progress(int size, int flag)
|
||||
@ -136,25 +145,6 @@ int do_fs_walk(char *file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void send_vmname(char *vmname)
|
||||
{
|
||||
char buf[FILECOPY_VMNAME_SIZE];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
strncat(buf, vmname, sizeof(buf) - 1);
|
||||
if (!write_all(1, buf, sizeof buf))
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char *get_item(char *data, char **current, int size)
|
||||
{
|
||||
char *ret;
|
||||
if ((unsigned long) *current >= (unsigned long) data + size)
|
||||
return NULL;
|
||||
ret = *current;
|
||||
*current += strlen(ret) + 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void notify_end_and_wait_for_result()
|
||||
{
|
||||
struct result_header hdr;
|
||||
@ -168,26 +158,40 @@ void notify_end_and_wait_for_result()
|
||||
|
||||
/* wait for result */
|
||||
if (!read_all(0, &hdr, sizeof(hdr))) {
|
||||
exit(1); // hopefully remote has produced error message
|
||||
exit(1); // hopefully remote has produced error message
|
||||
}
|
||||
if (hdr.error_code != 0) {
|
||||
gui_fatal("Error writing files: %s", strerror(hdr.error_code));
|
||||
gui_fatal("Error writing files: %s",
|
||||
strerror(hdr.error_code));
|
||||
}
|
||||
if (hdr.crc32 != crc32_sum) {
|
||||
gui_fatal("File transfer failed: checksum mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
void parse_entry(char *data, int datasize)
|
||||
char *get_abs_path(char *cwd, char *pathname)
|
||||
{
|
||||
char *current = data;
|
||||
char *vmname, *entry, *sep;
|
||||
vmname = get_item(data, ¤t, datasize);
|
||||
client_flags = get_item(data, ¤t, datasize);
|
||||
char *ret;
|
||||
if (pathname[0] == '/')
|
||||
return strdup(pathname);
|
||||
asprintf(&ret, "%s/%s", cwd, pathname);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
char *entry;
|
||||
char *cwd;
|
||||
char *sep;
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
notify_progress(0, PROGRESS_FLAG_INIT);
|
||||
send_vmname(vmname);
|
||||
crc32_sum = 0;
|
||||
while ((entry = get_item(data, ¤t, datasize))) {
|
||||
cwd = getcwd(NULL, 0);
|
||||
for (i = 1; i < argc; i++) {
|
||||
entry = get_abs_path(cwd, argv[i]);
|
||||
|
||||
do {
|
||||
sep = rindex(entry, '/');
|
||||
if (!sep)
|
||||
@ -200,53 +204,9 @@ void parse_entry(char *data, int datasize)
|
||||
else if (chdir(entry))
|
||||
gui_fatal("chdir to %s", entry);
|
||||
do_fs_walk(sep + 1);
|
||||
free(entry);
|
||||
}
|
||||
notify_end_and_wait_for_result();
|
||||
notify_progress(0, PROGRESS_FLAG_DONE);
|
||||
}
|
||||
|
||||
void process_spoolentry(char *entry_name)
|
||||
{
|
||||
char *abs_spool_entry_name;
|
||||
int entry_fd;
|
||||
struct stat st;
|
||||
char *entry;
|
||||
int entry_size;
|
||||
asprintf(&abs_spool_entry_name, "%s/%s", FILECOPY_SPOOL,
|
||||
entry_name);
|
||||
entry_fd = open(abs_spool_entry_name, O_RDONLY);
|
||||
unlink(abs_spool_entry_name);
|
||||
if (entry_fd < 0 || fstat(entry_fd, &st))
|
||||
gui_fatal("bad file copy spool entry");
|
||||
entry_size = st.st_size;
|
||||
entry = calloc(1, entry_size + 1);
|
||||
if (!entry)
|
||||
gui_fatal("malloc");
|
||||
if (!read_all(entry_fd, entry, entry_size))
|
||||
gui_fatal("read filecopy entry");
|
||||
close(entry_fd);
|
||||
parse_entry(entry, entry_size);
|
||||
}
|
||||
|
||||
void scan_spool(char *name)
|
||||
{
|
||||
struct dirent *ent;
|
||||
DIR *dir = opendir(name);
|
||||
if (!dir)
|
||||
gui_fatal("opendir %s", name);
|
||||
while ((ent = readdir(dir))) {
|
||||
char *fname = ent->d_name;
|
||||
if (fname[0] != '.') {
|
||||
process_spoolentry(fname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
scan_spool(FILECOPY_SPOOL);
|
||||
return 0;
|
||||
}
|
||||
|
@ -93,47 +93,11 @@ void talk_to_daemon(char *fname)
|
||||
recv_file(fname);
|
||||
}
|
||||
|
||||
void process_spoolentry(char *entry_name)
|
||||
{
|
||||
char *abs_spool_entry_name;
|
||||
int entry_fd;
|
||||
struct stat st;
|
||||
char *filename;
|
||||
int entry_size;
|
||||
asprintf(&abs_spool_entry_name, "%s/%s", DVM_SPOOL, entry_name);
|
||||
entry_fd = open(abs_spool_entry_name, O_RDONLY);
|
||||
unlink(abs_spool_entry_name);
|
||||
if (entry_fd < 0 || fstat(entry_fd, &st))
|
||||
gui_fatal("bad dvm_entry");
|
||||
entry_size = st.st_size;
|
||||
filename = calloc(1, entry_size + DVM_FILENAME_SIZE);
|
||||
if (!filename)
|
||||
gui_fatal("malloc");
|
||||
if (!read_all(entry_fd, filename, entry_size))
|
||||
gui_fatal("read dvm entry %s", abs_spool_entry_name);
|
||||
close(entry_fd);
|
||||
talk_to_daemon(filename);
|
||||
}
|
||||
|
||||
void scan_spool(char *name)
|
||||
{
|
||||
struct dirent *ent;
|
||||
DIR *dir = opendir(name);
|
||||
if (!dir)
|
||||
gui_fatal("opendir %s", name);
|
||||
while ((ent = readdir(dir))) {
|
||||
char *fname = ent->d_name;
|
||||
if (!strcmp(fname, ".") || !strcmp(fname, ".."))
|
||||
continue;
|
||||
process_spoolentry(fname);
|
||||
break;
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
scan_spool(DVM_SPOOL);
|
||||
if (argc!=2)
|
||||
gui_fatal("OpenInVM - no file given?");
|
||||
talk_to_daemon(argv[1]);
|
||||
return 0;
|
||||
}
|
||||
}
|
1
appvm/qubes.Filecopy
Normal file
1
appvm/qubes.Filecopy
Normal file
@ -0,0 +1 @@
|
||||
/usr/lib/qubes/qfile-unpacker
|
1
appvm/qubes.Filecopy.policy
Normal file
1
appvm/qubes.Filecopy.policy
Normal file
@ -0,0 +1 @@
|
||||
anyvm anyvm ask,user=root
|
1
appvm/qubes.OpenInVM
Normal file
1
appvm/qubes.OpenInVM
Normal file
@ -0,0 +1 @@
|
||||
/usr/lib/qubes/vm-file-editor
|
2
appvm/qubes.OpenInVM.policy
Normal file
2
appvm/qubes.OpenInVM.policy
Normal file
@ -0,0 +1,2 @@
|
||||
anyvm dispvm allow
|
||||
anyvm anyvm ask
|
@ -20,50 +20,24 @@
|
||||
#
|
||||
#
|
||||
|
||||
if [ x"$1" = "x--without-progress" ] ; then
|
||||
DO_PROGRESS=0
|
||||
shift
|
||||
else
|
||||
DO_PROGRESS=1
|
||||
fi
|
||||
|
||||
if [ $# -lt 2 ] ; then
|
||||
echo usage: $0 '[--without-progress] dest_vmname file [file]+'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ x"$1" = "x--without-progress" ] ; then
|
||||
export PROGRESS_TYPE=none
|
||||
shift
|
||||
else
|
||||
export PROGRESS_TYPE=console
|
||||
fi
|
||||
|
||||
|
||||
VM="$1"
|
||||
shift
|
||||
|
||||
if [ $DO_PROGRESS = 1 ] ; then
|
||||
SIZE=$(du --apparent-size -c "$@" | tail -1 | cut -f 1)
|
||||
if [ $PROGRESS_TYPE = console ] ; then
|
||||
export FILECOPY_TOTAL_SIZE=$(du --apparent-size -c "$@" | tail -1 | cut -f 1)
|
||||
fi
|
||||
|
||||
export PROGRESS_FILE=$(mktemp)
|
||||
/usr/lib/qubes/qvm-trigger-copy-to-vm $VM "$@"
|
||||
while ! [ -s $PROGRESS_FILE ] ; do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
while true ; do
|
||||
read agentpid sentsize agentstatus < $PROGRESS_FILE
|
||||
if [ "x"$agentstatus = x ] ; then continue ; fi
|
||||
if ! [ -e /proc/$agentpid ] ; then break ; fi
|
||||
if [ "x"$agentstatus = xDONE ] ; then break ; fi
|
||||
CURRSIZE=$(($sentsize/1024))
|
||||
if [ $DO_PROGRESS = 1 ] ; then
|
||||
echo -ne "\r sent $CURRSIZE/$SIZE KB "
|
||||
fi
|
||||
sleep 0.4
|
||||
done
|
||||
|
||||
rm -f $PROGRESS_FILE
|
||||
if [ $DO_PROGRESS = 1 ] ; then
|
||||
echo
|
||||
fi
|
||||
|
||||
if ! [ "x"$agentstatus = xDONE ] ; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
exec /usr/lib/qubes/qrexec_client_vm $VM qubes.Filecopy /usr/lib/qubes/qfile-agent "$@"
|
||||
|
@ -25,19 +25,10 @@ if [ X$VM = X ] ; then exit 0 ; fi
|
||||
|
||||
SIZE=$(du --apparent-size -c "$@" | tail -1 | cut -f 1)
|
||||
|
||||
export PROGRESS_FILE=$(mktemp)
|
||||
/usr/lib/qubes/qvm-trigger-copy-to-vm $VM "$@"
|
||||
while ! [ -s $PROGRESS_FILE ] ; do
|
||||
sleep 0.1
|
||||
done
|
||||
(while true ; do
|
||||
read agentpid sentsize agentstatus < $PROGRESS_FILE
|
||||
if [ "x"$agentstatus = x ] ; then continue ; fi
|
||||
if ! [ -e /proc/$agentpid ] ; then break ; fi
|
||||
if [ "x"$agentstatus = xdone ] ; then break ; fi
|
||||
export PROGRESS_TYPE=gui
|
||||
|
||||
/usr/lib/qubes/qrexec_client_vm $VM qubes.Filecopy /usr/lib/qubes/qfile-agent "$@" |
|
||||
(while read sentsize ; do
|
||||
CURRSIZE=$(($sentsize/1024))
|
||||
echo $((100*$CURRSIZE/$SIZE))
|
||||
sleep 0.1
|
||||
done) | zenity --progress --text="Copying files to domain: $VM..." --auto-close
|
||||
|
||||
rm -f $PROGRESS_FILE
|
||||
|
@ -27,23 +27,16 @@ SIZE=$(du --apparent-size -c "$@" | tail -1 | cut -f 1)
|
||||
REF=$(kdialog --progressbar "Copy progress")
|
||||
qdbus $REF org.freedesktop.DBus.Properties.Set "" maximum $SIZE
|
||||
|
||||
export PROGRESS_FILE=$(mktemp)
|
||||
/usr/lib/qubes/qvm-trigger-copy-to-vm $VM "$@"
|
||||
while ! [ -s $PROGRESS_FILE ] ; do
|
||||
sleep 0.1
|
||||
done
|
||||
while true ; do
|
||||
read agentpid sentsize agentstatus < $PROGRESS_FILE
|
||||
if [ "x"$agentstatus = x ] ; then continue ; fi
|
||||
if ! [ -e /proc/$agentpid ] ; then break ; fi
|
||||
if [ "x"$agentstatus = xdone ] ; then break ; fi
|
||||
export PROGRESS_TYPE=gui
|
||||
|
||||
/usr/lib/qubes/qrexec_client_vm $VM qubes.Filecopy \
|
||||
/usr/lib/qubes/qfile-agent "$@" |
|
||||
(while read sentsize ; do
|
||||
CURRSIZE=$(($sentsize/1024))
|
||||
qdbus $REF org.freedesktop.DBus.Properties.Set "" value $CURRSIZE
|
||||
sleep 0.4
|
||||
done
|
||||
done)
|
||||
|
||||
qdbus $REF close
|
||||
rm -f $PROGRESS_FILE
|
||||
# we do not want a dozen error messages, do we
|
||||
# if ! [ "x"$agentstatus = xDONE ] ; then
|
||||
# kdialog --sorry 'Abnormal file copy termination; see /var/log/qubes/qrexec.xid.log in dom0 for more details'
|
||||
|
@ -25,16 +25,4 @@ if ! [ $# = 1 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILE="$1"
|
||||
if ! [ "X""${FILE:0:1}" = X/ ] ; then
|
||||
FILE="$PWD"/"$1"
|
||||
fi
|
||||
|
||||
DVMSPOOL=/home/user/.dvmspool
|
||||
if ! [ -e $DVMSPOOL ] ; then
|
||||
mkdir $DVMSPOOL || exit 1
|
||||
fi
|
||||
|
||||
echo -n "$FILE" > $DVMSPOOL/req.$$
|
||||
echo -n DVMR > /var/run/qubes/qrexec_agent
|
||||
|
||||
exec /usr/lib/qubes/qrexec_client_vm dispvm qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" "$1"
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
@ -20,27 +20,9 @@
|
||||
#
|
||||
#
|
||||
|
||||
if [ $# -lt 2 ] ; then
|
||||
echo usage: $0 'vmname file [file]*'
|
||||
if ! [ $# = 2 ] ; then
|
||||
echo "Usage: $0 vmname filename"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FILECOPY_SPOOL=/home/user/.filecopyspool
|
||||
if ! [ -e $FILECOPY_SPOOL ] ; then
|
||||
mkdir $FILECOPY_SPOOL
|
||||
fi
|
||||
|
||||
REQ_FILE_TMP=$FILECOPY_SPOOL/.req.$$
|
||||
echo -ne "$1""\x00" > $REQ_FILE_TMP
|
||||
echo -ne "$PROGRESS_FILE""\x00" >> $REQ_FILE_TMP
|
||||
|
||||
shift
|
||||
for FILE in "$@" ; do
|
||||
if ! [ "X""${FILE:0:1}" = X/ ] ; then
|
||||
FILE="$PWD"/"$FILE"
|
||||
fi
|
||||
echo -ne "$FILE""\x00" >> $REQ_FILE_TMP
|
||||
done
|
||||
|
||||
mv $REQ_FILE_TMP $FILECOPY_SPOOL/req.$$
|
||||
echo -n FCPR > /var/run/qubes/qrexec_agent
|
||||
exec /usr/lib/qubes/qrexec_client_vm "$1" qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" "$2"
|
@ -67,4 +67,4 @@ else
|
||||
fi
|
||||
|
||||
# qvm-copy-to-vm works only from user
|
||||
su -c "qvm-copy-to-vm @dom0updates $DOM0_UPDATES_DIR/packages/*.rpm" user
|
||||
su -c "/usr/lib/qubes/qrexec_client_vm dom0 qubes.ReceiveUpdates /usr/lib/qubes/qfile-agent $DOM0_UPDATES_DIR/packages/*.rpm" user
|
||||
|
@ -3,5 +3,5 @@
|
||||
UPDATEABLE=`/usr/bin/xenstore-read qubes_vm_updateable`
|
||||
|
||||
if [ "$UPDATEABLE" = "True" ]; then
|
||||
echo -n SYNC > /var/run/qubes/qrexec_agent
|
||||
/usr/lib/qubes/qrexec_client_vm dom0 qubes.SyncAppMenus /bin/grep -H = /usr/share/applications/*.desktop
|
||||
fi
|
||||
|
@ -29,15 +29,6 @@ from qubes.qubes import QubesVmCollection
|
||||
updates_dir = "/var/lib/qubes/updates"
|
||||
updates_rpm_dir = updates_dir + "/rpm"
|
||||
|
||||
def is_copy_allowed(vm):
|
||||
# if vm.copy_allowed:
|
||||
# return True
|
||||
q = 'Do you authorize file copy from '
|
||||
q+= os.getenv("QREXEC_REMOTE_DOMAIN")
|
||||
q+= ' to ' + vm.name + ' ?'
|
||||
retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'File transfer confirmation'])
|
||||
return retcode == 0
|
||||
|
||||
def dom0updates_fatal(msg):
|
||||
print >> sys.stderr, msg
|
||||
shutil.rmtree(updates_rpm_dir)
|
||||
@ -69,31 +60,13 @@ def handle_dom0updates(updatevm):
|
||||
exit(0)
|
||||
|
||||
def main():
|
||||
FILECOPY_VMNAME_SIZE = 32
|
||||
blob=os.read(0, FILECOPY_VMNAME_SIZE)
|
||||
vmname = blob.split("\x00")[0]
|
||||
|
||||
qvm_collection = QubesVmCollection()
|
||||
qvm_collection.lock_db_for_reading()
|
||||
qvm_collection.load()
|
||||
qvm_collection.unlock_db()
|
||||
|
||||
if vmname == '@dom0updates':
|
||||
updatevm = qvm_collection.get_updatevm_vm()
|
||||
handle_dom0updates(updatevm)
|
||||
# handle_dom0updates never returns
|
||||
|
||||
vm = qvm_collection.get_vm_by_name(vmname)
|
||||
# we do not want to flood dom0 with error windows; so just log to stderr
|
||||
if vm is None:
|
||||
print >> sys.stderr, 'Domain ' + vmname + ' does not exist ?'
|
||||
exit(1)
|
||||
if not vm.is_running():
|
||||
print >> sys.stderr, 'Domain ' + vmname + ' is not running ?'
|
||||
exit(1)
|
||||
if not is_copy_allowed(vm):
|
||||
exit(1)
|
||||
cmd = "root:/usr/lib/qubes/qfile-unpacker " + os.getenv("QREXEC_REMOTE_DOMAIN")
|
||||
os.execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", vmname, cmd)
|
||||
updatevm = qvm_collection.get_updatevm_vm()
|
||||
handle_dom0updates(updatevm)
|
||||
|
||||
main()
|
1
dom0/aux-tools/qubes.ReceiveUpdates
Normal file
1
dom0/aux-tools/qubes.ReceiveUpdates
Normal file
@ -0,0 +1 @@
|
||||
/usr/lib/qubes/qubes-receive-updates
|
1
dom0/aux-tools/qubes.ReceiveUpdates.policy
Normal file
1
dom0/aux-tools/qubes.ReceiveUpdates.policy
Normal file
@ -0,0 +1 @@
|
||||
anyvm dom0 allow
|
1
dom0/qubes.SyncAppMenus
Normal file
1
dom0/qubes.SyncAppMenus
Normal file
@ -0,0 +1 @@
|
||||
/usr/bin/qvm-sync-appmenus
|
1
dom0/qubes.SyncAppMenus.policy
Normal file
1
dom0/qubes.SyncAppMenus.policy
Normal file
@ -0,0 +1 @@
|
||||
anyvm dom0 allow
|
@ -124,15 +124,20 @@ class QfileDaemonDvm:
|
||||
|
||||
def main():
|
||||
global notify_object
|
||||
exec_index = sys.argv[1]
|
||||
src_vmname = sys.argv[2]
|
||||
user = sys.argv[3]
|
||||
|
||||
notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
||||
qfile = QfileDaemonDvm(os.getenv("QREXEC_REMOTE_DOMAIN"))
|
||||
qfile = QfileDaemonDvm(src_vmname)
|
||||
lockf = open("/var/run/qubes/qfile-daemon-dvm.lock", 'a')
|
||||
fcntl.fcntl(lockf, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
|
||||
fcntl.flock(lockf, fcntl.LOCK_EX)
|
||||
dispname = qfile.get_dvm()
|
||||
lockf.close()
|
||||
if dispname is not None:
|
||||
subprocess.call(['/usr/lib/qubes/qrexec_client', '-d', dispname, 'directly:user:/usr/lib/qubes/dvm_file_editor'])
|
||||
subprocess.call(['/usr/lib/qubes/qrexec_client', '-d', dispname,
|
||||
user+":exec /usr/lib/qubes/qubes_rpc_multiplexer ' + exec_index + " " + src_vmname])
|
||||
subprocess.call(['/usr/sbin/xl', 'destroy', dispname])
|
||||
qfile.remove_disposable_from_qdb(dispname)
|
||||
|
||||
|
@ -3,12 +3,14 @@ CFLAGS+=-g -Wall -I../vchan -I../common -pie -fPIC
|
||||
XENLIBS=-lvchan -lu2mfn -lxenstore -lxenctrl
|
||||
COMMONIOALL=../common/ioall.o
|
||||
|
||||
all: qrexec_daemon qrexec_agent qrexec_client
|
||||
all: qrexec_daemon qrexec_agent qrexec_client qrexec_client_vm
|
||||
qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o
|
||||
$(CC) -pie -L../vchan -L../u2mfn -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS)
|
||||
qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL)
|
||||
$(CC) -pie -L../vchan -L../u2mfn -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS)
|
||||
qrexec_agent: qrexec_agent.o unix_server.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL)
|
||||
$(CC) -pie -L../vchan -L../u2mfn -g -o qrexec_agent qrexec_agent.o unix_server.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS)
|
||||
qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o
|
||||
$(CC) -pie -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o
|
||||
qrexec_client_vm: qrexec_client_vm.o
|
||||
$(CC) -pie -g -o qrexec_client_vm qrexec_client_vm.o
|
||||
clean:
|
||||
rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client
|
||||
rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client qrexec_client_vm
|
||||
|
64
qrexec/README.rpc
Normal file
64
qrexec/README.rpc
Normal file
@ -0,0 +1,64 @@
|
||||
Currently (after commit 2600134e3bb781fca25fe77e464f8b875741dc83),
|
||||
qrexec_agent can request a service (specified by a "exec_index") to be
|
||||
executed on a different VM or dom0. Access control is enforced in dom0 via
|
||||
files in /etc/qubes_rpc/policy. File copy, Open in Dispvm, sync appmenus,
|
||||
upload updates to dom0 - they all have been ported to the new API.
|
||||
See the quick HOWTO section on how to add a new service. Note we have
|
||||
qvm-open-in-vm utility practically for free.
|
||||
|
||||
CHANGES
|
||||
|
||||
Besides flexibility offered by /etc/qubes_rpc/policy, writing a client
|
||||
is much simpler now. The workflow used to be (using "filecopy" service as
|
||||
an example):
|
||||
a) "filecopy_ui" process places job description in some spool directory,
|
||||
signals qrexec_agent to signal qrexec_daemon
|
||||
b) qrexec_daemon executes "qrexec_client -d domain filecopy_worker ...."
|
||||
and "filecopy_worker" process needed to parse spool and retrieve job
|
||||
description from there. Particularly, "filecopy_ui" had no connection to
|
||||
remote.
|
||||
Now, the flow is:
|
||||
a) qrexec_client_vm process obtains 3 unix socket descriptors from
|
||||
qrexec_agent, dup stdin/out/err to them; forms "existing_process_handle" from
|
||||
them
|
||||
b) qrexec_client_vm signals qrexec_agent to signal qrexec_daemon, with a
|
||||
"exec_index" (so, type of service) as an argument
|
||||
c) qrexec_daemon executed "qrexec_client -d domain -c existing_process_handle ...."
|
||||
d) qrexec_client_vm execve filecopy_program.
|
||||
|
||||
Thus, there is only one service program, and it has direct access to remote via
|
||||
stdin/stdout.
|
||||
|
||||
HOWTO
|
||||
|
||||
Let's add a new "test.Add" service, that will add two numbers. We need the
|
||||
following files in the template fs:
|
||||
==========================
|
||||
/usr/bin/our_test_add_client:
|
||||
#!/bin/sh
|
||||
echo $1 $2
|
||||
exec cat >&2
|
||||
# more correct: exec cat >&$SAVED_FD_1, but do not scare the reader
|
||||
==========================
|
||||
/usr/bin/our_test_add_server:
|
||||
#!/bin/sh
|
||||
read arg1 arg2
|
||||
echo $(($arg1+$arg2))
|
||||
==========================
|
||||
/etc/qubes_rpc/test.Add:
|
||||
/usr/bin/our_test_add_server
|
||||
|
||||
Now, on the client side, we start the client via
|
||||
/usr/lib/qubes/qrexec_client_vm target_vm test.Add /usr/bin/our_test_add_client 11 22
|
||||
|
||||
Because there is no policy yet, dom0 will ask you to create one (of cource you
|
||||
can do it before the first run of our_test_add_client). So, in dom0, create (by now,
|
||||
with a file editor) the /etc/qubes_rpc/policy/test.Add file with
|
||||
anyvm anyvm ask
|
||||
content. The format of the /etc/qubes_rpc/policy/* files is
|
||||
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
|
||||
|
||||
You can specify srcvm and destvm by name, or by one of "anyvm", "dispvm", "dom0"
|
||||
reserved keywords.
|
||||
Then, when you confirm the operation, you will get the result in the client vm.
|
||||
|
@ -32,7 +32,7 @@ int write_all_vchan_ext(void *buf, int size);
|
||||
int buffer_space_vchan_ext();
|
||||
void fix_fds(int fdin, int fdout, int fderr);
|
||||
|
||||
int get_server_socket(int domid, char * domname);
|
||||
int get_server_socket(char *);
|
||||
int do_accept(int s);
|
||||
|
||||
enum {
|
||||
|
@ -26,11 +26,14 @@
|
||||
#define REXEC_PORT 512
|
||||
|
||||
#define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec_agent"
|
||||
#define QREXEC_AGENT_FDPASS_PATH "/var/run/qubes/qrexec_agent_fdpass"
|
||||
|
||||
enum {
|
||||
MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100,
|
||||
MSG_CLIENT_TO_SERVER_JUST_EXEC,
|
||||
MSG_CLIENT_TO_SERVER_CONNECT_EXISTING,
|
||||
|
||||
MSG_SERVER_TO_AGENT_CONNECT_EXISTING,
|
||||
MSG_SERVER_TO_AGENT_EXEC_CMDLINE,
|
||||
MSG_SERVER_TO_AGENT_JUST_EXEC,
|
||||
MSG_SERVER_TO_AGENT_INPUT,
|
||||
@ -42,19 +45,13 @@ enum {
|
||||
MSG_AGENT_TO_SERVER_STDOUT,
|
||||
MSG_AGENT_TO_SERVER_STDERR,
|
||||
MSG_AGENT_TO_SERVER_EXIT_CODE,
|
||||
MSG_AGENT_TO_SERVER_TRIGGER_EXEC,
|
||||
MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING,
|
||||
|
||||
MSG_SERVER_TO_CLIENT_STDOUT,
|
||||
MSG_SERVER_TO_CLIENT_STDERR,
|
||||
MSG_SERVER_TO_CLIENT_EXIT_CODE
|
||||
};
|
||||
|
||||
enum {
|
||||
QREXEC_EXECUTE_FILE_COPY=0x700,
|
||||
QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM,
|
||||
QREXEC_EXECUTE_APPMENUS_SYNC
|
||||
};
|
||||
|
||||
struct server_header {
|
||||
unsigned int type;
|
||||
unsigned int client_id;
|
||||
@ -65,3 +62,13 @@ struct client_header {
|
||||
unsigned int type;
|
||||
unsigned int len;
|
||||
};
|
||||
|
||||
struct connect_existing_params {
|
||||
char ident[32];
|
||||
};
|
||||
|
||||
struct trigger_connect_params {
|
||||
char exec_index[64];
|
||||
char target_vmname[32];
|
||||
struct connect_existing_params process_fds;
|
||||
};
|
||||
|
@ -68,12 +68,14 @@ struct _process_fd process_fd[MAX_FDS];
|
||||
struct _client_info client_info[MAX_FDS];
|
||||
|
||||
int trigger_fd;
|
||||
int passfd_socket;
|
||||
|
||||
void init()
|
||||
{
|
||||
peer_server_init(REXEC_PORT);
|
||||
umask(0);
|
||||
mkfifo(QREXEC_AGENT_TRIGGER_PATH, 0666);
|
||||
passfd_socket = get_server_socket(QREXEC_AGENT_FDPASS_PATH);
|
||||
umask(077);
|
||||
trigger_fd =
|
||||
open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK);
|
||||
@ -145,15 +147,9 @@ void handle_just_exec(int client_id, int len)
|
||||
fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid);
|
||||
}
|
||||
|
||||
void handle_exec(int client_id, int len)
|
||||
void create_info_about_client(int client_id, int pid, int stdin_fd,
|
||||
int stdout_fd, int stderr_fd)
|
||||
{
|
||||
char buf[len];
|
||||
int pid, stdin_fd, stdout_fd, stderr_fd;
|
||||
|
||||
read_all_vchan_ext(buf, len);
|
||||
|
||||
do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
|
||||
|
||||
process_fd[stdout_fd].client_id = client_id;
|
||||
process_fd[stdout_fd].type = FDTYPE_STDOUT;
|
||||
process_fd[stdout_fd].is_blocked = 0;
|
||||
@ -177,11 +173,34 @@ void handle_exec(int client_id, int len)
|
||||
client_info[client_id].is_blocked = 0;
|
||||
client_info[client_id].is_close_after_flush_needed = 0;
|
||||
buffer_init(&client_info[client_id].buffer);
|
||||
}
|
||||
|
||||
void handle_exec(int client_id, int len)
|
||||
{
|
||||
char buf[len];
|
||||
int pid, stdin_fd, stdout_fd, stderr_fd;
|
||||
|
||||
read_all_vchan_ext(buf, len);
|
||||
|
||||
do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
|
||||
|
||||
create_info_about_client(client_id, pid, stdin_fd, stdout_fd,
|
||||
stderr_fd);
|
||||
|
||||
fprintf(stderr, "executed %s pid %d\n", buf, pid);
|
||||
|
||||
}
|
||||
|
||||
void handle_connect_existing(int client_id, int len)
|
||||
{
|
||||
int stdin_fd, stdout_fd, stderr_fd;
|
||||
char buf[len];
|
||||
read_all_vchan_ext(buf, len);
|
||||
sscanf(buf, "%d %d %d", &stdin_fd, &stdout_fd, &stderr_fd);
|
||||
create_info_about_client(client_id, -1, stdin_fd, stdout_fd,
|
||||
stderr_fd);
|
||||
client_info[client_id].is_exited = 1; //do not wait for SIGCHLD
|
||||
}
|
||||
|
||||
void update_max_process_fd()
|
||||
{
|
||||
@ -307,6 +326,9 @@ void handle_server_data()
|
||||
case MSG_XOFF:
|
||||
set_blocked_outerr(s_hdr.client_id, 1);
|
||||
break;
|
||||
case MSG_SERVER_TO_AGENT_CONNECT_EXISTING:
|
||||
handle_connect_existing(s_hdr.client_id, s_hdr.len);
|
||||
break;
|
||||
case MSG_SERVER_TO_AGENT_EXEC_CMDLINE:
|
||||
handle_exec(s_hdr.client_id, s_hdr.len);
|
||||
break;
|
||||
@ -432,9 +454,12 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
|
||||
FD_SET(trigger_fd, rdset);
|
||||
if (trigger_fd > max)
|
||||
max = trigger_fd;
|
||||
FD_SET(passfd_socket, rdset);
|
||||
if (passfd_socket > max)
|
||||
max = passfd_socket;
|
||||
|
||||
for (i = 0; i < MAX_FDS; i++)
|
||||
if (client_info[i].pid > 0 && client_info[i].is_blocked) {
|
||||
if (client_info[i].pid && client_info[i].is_blocked) {
|
||||
fd = client_info[i].stdin_fd;
|
||||
FD_SET(fd, wrset);
|
||||
if (fd > max)
|
||||
@ -467,28 +492,31 @@ void flush_client_data_agent(int client_id)
|
||||
}
|
||||
}
|
||||
|
||||
void handle_new_passfd()
|
||||
{
|
||||
int fd = do_accept(passfd_socket);
|
||||
if (fd >= MAX_FDS) {
|
||||
fprintf(stderr, "too many clients ?\n");
|
||||
exit(1);
|
||||
}
|
||||
// let client know what fd has been allocated
|
||||
write(fd, &fd, sizeof(fd));
|
||||
}
|
||||
|
||||
|
||||
void handle_trigger_io()
|
||||
{
|
||||
struct server_header s_hdr;
|
||||
char buf[5];
|
||||
struct trigger_connect_params params;
|
||||
int ret;
|
||||
|
||||
s_hdr.client_id = 0;
|
||||
s_hdr.len = 0;
|
||||
if ((ret = read(trigger_fd, buf, 4)) == 4) {
|
||||
buf[4] = 0;
|
||||
if (!strcmp(buf, "FCPR"))
|
||||
s_hdr.client_id = QREXEC_EXECUTE_FILE_COPY;
|
||||
else if (!strcmp(buf, "DVMR"))
|
||||
s_hdr.client_id =
|
||||
QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM;
|
||||
else if (!strcmp(buf, "SYNC"))
|
||||
s_hdr.client_id =
|
||||
QREXEC_EXECUTE_APPMENUS_SYNC;
|
||||
if (s_hdr.client_id) {
|
||||
s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_EXEC;
|
||||
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
|
||||
}
|
||||
ret = read(trigger_fd, ¶ms, sizeof(params));
|
||||
if (ret == sizeof(params)) {
|
||||
s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING;
|
||||
write_all_vchan_ext(&s_hdr, sizeof s_hdr);
|
||||
write_all_vchan_ext(¶ms, sizeof params);
|
||||
}
|
||||
// trigger_fd is nonblock - so no need to reopen
|
||||
// not really, need to reopen at EOF
|
||||
@ -518,6 +546,9 @@ int main()
|
||||
|
||||
wait_for_vchan_or_argfd(max, &rdset, &wrset);
|
||||
|
||||
if (FD_ISSET(passfd_socket, &rdset))
|
||||
handle_new_passfd();
|
||||
|
||||
while (read_ready_vchan_ext())
|
||||
handle_server_data();
|
||||
|
||||
@ -526,7 +557,7 @@ int main()
|
||||
|
||||
handle_process_data_all(&rdset);
|
||||
for (i = 0; i <= MAX_FDS; i++)
|
||||
if (client_info[i].pid > 0
|
||||
if (client_info[i].pid
|
||||
&& client_info[i].is_blocked
|
||||
&& FD_ISSET(client_info[i].stdin_fd, &wrset))
|
||||
flush_client_data_agent(i);
|
||||
|
@ -209,8 +209,9 @@ void select_loop(int s)
|
||||
void usage(char *name)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: %s -d domain_num [-l local_prog] -e remote_cmdline\n"
|
||||
"-e means exit after sending cmd\n", name);
|
||||
"usage: %s -d domain_num [-l local_prog] -e -c remote_cmdline\n"
|
||||
"-e means exit after sending cmd, -c: connect to existing process\n",
|
||||
name);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -220,8 +221,9 @@ int main(int argc, char **argv)
|
||||
char *domname = NULL;
|
||||
int s;
|
||||
int just_exec = 0;
|
||||
int connect_existing = 0;
|
||||
char *local_cmdline = NULL;
|
||||
while ((opt = getopt(argc, argv, "d:l:e")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "d:l:ec")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
domname = strdup(optarg);
|
||||
@ -232,6 +234,9 @@ int main(int argc, char **argv)
|
||||
case 'e':
|
||||
just_exec = 1;
|
||||
break;
|
||||
case 'c':
|
||||
connect_existing = 1;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
}
|
||||
@ -247,8 +252,12 @@ int main(int argc, char **argv)
|
||||
send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC,
|
||||
argv[optind]);
|
||||
else {
|
||||
send_cmdline(s, MSG_CLIENT_TO_SERVER_EXEC_CMDLINE,
|
||||
argv[optind]);
|
||||
int cmd;
|
||||
if (connect_existing)
|
||||
cmd = MSG_CLIENT_TO_SERVER_CONNECT_EXISTING;
|
||||
else
|
||||
cmd = MSG_CLIENT_TO_SERVER_EXEC_CMDLINE;
|
||||
send_cmdline(s, cmd, argv[optind]);
|
||||
select_loop(s);
|
||||
}
|
||||
return 0;
|
||||
|
109
qrexec/qrexec_client_vm.c
Normal file
109
qrexec/qrexec_client_vm.c
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include "qrexec.h"
|
||||
int connect_unix_socket()
|
||||
{
|
||||
int s, len;
|
||||
struct sockaddr_un remote;
|
||||
|
||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
remote.sun_family = AF_UNIX;
|
||||
strncpy(remote.sun_path, QREXEC_AGENT_FDPASS_PATH,
|
||||
sizeof(remote.sun_path));
|
||||
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
|
||||
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
|
||||
perror("connect");
|
||||
exit(1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
char *get_program_name(char *prog)
|
||||
{
|
||||
char *basename = rindex(prog, '/');
|
||||
if (basename)
|
||||
return basename + 1;
|
||||
else
|
||||
return prog;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int trigger_fd;
|
||||
struct trigger_connect_params params;
|
||||
int local_fd[3], remote_fd[3];
|
||||
int i;
|
||||
char *abs_exec_path;
|
||||
|
||||
if (argc < 4) {
|
||||
fprintf(stderr,
|
||||
"usage: %s target_vmname program_ident local_program [local program arguments]\n",
|
||||
argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
trigger_fd = open(QREXEC_AGENT_TRIGGER_PATH, O_WRONLY);
|
||||
if (trigger_fd < 0) {
|
||||
perror("open QREXEC_AGENT_TRIGGER_PATH");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
local_fd[i] = connect_unix_socket();
|
||||
read(local_fd[i], &remote_fd[i], sizeof(remote_fd[i]));
|
||||
if (i != 2 || getenv("PASS_LOCAL_STDERR")) {
|
||||
char *env;
|
||||
asprintf(&env, "SAVED_FD_%d=%d", i, dup(i));
|
||||
putenv(env);
|
||||
dup2(local_fd[i], i);
|
||||
close(local_fd[i]);
|
||||
}
|
||||
}
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
strncpy(params.exec_index, argv[2], sizeof(params.exec_index));
|
||||
strncpy(params.target_vmname, argv[1],
|
||||
sizeof(params.target_vmname));
|
||||
snprintf(params.process_fds.ident,
|
||||
sizeof(params.process_fds.ident), "%d %d %d",
|
||||
remote_fd[0], remote_fd[1], remote_fd[2]);
|
||||
|
||||
write(trigger_fd, ¶ms, sizeof(params));
|
||||
close(trigger_fd);
|
||||
|
||||
abs_exec_path = strdup(argv[3]);
|
||||
argv[3] = get_program_name(argv[3]);
|
||||
execv(abs_exec_path, argv + 3);
|
||||
perror("execv");
|
||||
return 1;
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <ioall.h>
|
||||
#include <string.h>
|
||||
#include "qrexec.h"
|
||||
#include "buffer.h"
|
||||
#include "glue.h"
|
||||
@ -67,6 +68,21 @@ void sigchld_handler(int x);
|
||||
|
||||
char *remote_domain_name; // guess what
|
||||
|
||||
int create_qrexec_socket(int domid, 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);
|
||||
symlink(socket_address, link_to_socket_name);
|
||||
return get_server_socket(socket_address);
|
||||
}
|
||||
|
||||
|
||||
/* do the preparatory tasks, needed before entering the main event loop */
|
||||
void init(int xid)
|
||||
{
|
||||
@ -119,7 +135,7 @@ void init(int xid)
|
||||
/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
|
||||
umask(0);
|
||||
qrexec_daemon_unix_socket_fd =
|
||||
get_server_socket(xid, remote_domain_name);
|
||||
create_qrexec_socket(xid, remote_domain_name);
|
||||
umask(0077);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGCHLD, sigchld_handler);
|
||||
@ -167,8 +183,7 @@ void terminate_client_and_flush_data(int fd)
|
||||
write_all_vchan_ext(&s_hdr, sizeof(s_hdr));
|
||||
}
|
||||
|
||||
void get_cmdline_body_from_client_and_pass_to_agent(int fd,
|
||||
struct server_header
|
||||
void get_cmdline_body_from_client_and_pass_to_agent(int fd, struct server_header
|
||||
*s_hdr)
|
||||
{
|
||||
int len = s_hdr->len;
|
||||
@ -196,6 +211,9 @@ void handle_cmdline_message_from_client(int fd)
|
||||
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;
|
||||
@ -271,8 +289,7 @@ void write_buffered_data_to_client(int client_id)
|
||||
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
|
||||
void get_packet_data_from_agent_and_pass_to_client(int client_id, struct client_header
|
||||
*hdr)
|
||||
{
|
||||
int len = hdr->len;
|
||||
@ -342,33 +359,48 @@ void check_children_count_and_wait_if_too_many()
|
||||
}
|
||||
}
|
||||
|
||||
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 == ' ')
|
||||
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.
|
||||
*/
|
||||
|
||||
void handle_execute_predefined_command(int req)
|
||||
void handle_execute_predefined_command()
|
||||
{
|
||||
char *rcmd = NULL, *lcmd = NULL;
|
||||
int i;
|
||||
struct trigger_connect_params untrusted_params, params;
|
||||
|
||||
check_children_count_and_wait_if_too_many();
|
||||
switch (req) {
|
||||
case QREXEC_EXECUTE_FILE_COPY:
|
||||
rcmd = "directly:user:/usr/lib/qubes/qfile-agent";
|
||||
lcmd = "/usr/lib/qubes/qfile-daemon";
|
||||
break;
|
||||
case QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM:
|
||||
rcmd = "directly:user:/usr/lib/qubes/qfile-agent-dvm";
|
||||
lcmd = "/usr/lib/qubes/qfile-daemon-dvm";
|
||||
break;
|
||||
case QREXEC_EXECUTE_APPMENUS_SYNC:
|
||||
rcmd = "user:grep -H = /usr/share/applications/*.desktop";
|
||||
lcmd = "/usr/bin/qvm-sync-appmenus";
|
||||
break;
|
||||
default: /* cannot happen, already sanitized */
|
||||
fprintf(stderr, "got trigger exec no %d\n", req);
|
||||
exit(1);
|
||||
}
|
||||
read_all_vchan_ext(&untrusted_params, sizeof(params));
|
||||
|
||||
/* 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");
|
||||
@ -383,8 +415,9 @@ void handle_execute_predefined_command(int req)
|
||||
close(i);
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d",
|
||||
remote_domain_name, "-l", lcmd, rcmd, NULL);
|
||||
execl("/usr/lib/qubes/qrexec_policy", "qrexec_policy",
|
||||
remote_domain_name, params.target_vmname,
|
||||
params.exec_index, params.process_fds.ident, NULL);
|
||||
perror("execl");
|
||||
exit(1);
|
||||
}
|
||||
@ -401,18 +434,8 @@ void check_client_id_in_range(unsigned int untrusted_client_id)
|
||||
|
||||
void sanitize_message_from_agent(struct server_header *untrusted_header)
|
||||
{
|
||||
int untrusted_cmd;
|
||||
switch (untrusted_header->type) {
|
||||
case MSG_AGENT_TO_SERVER_TRIGGER_EXEC:
|
||||
untrusted_cmd = untrusted_header->client_id;
|
||||
if (untrusted_cmd != QREXEC_EXECUTE_FILE_COPY &&
|
||||
untrusted_cmd != QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM &&
|
||||
untrusted_cmd != QREXEC_EXECUTE_APPMENUS_SYNC) {
|
||||
fprintf(stderr,
|
||||
"received MSG_AGENT_TO_SERVER_TRIGGER_EXEC cmd %d ?\n",
|
||||
untrusted_cmd);
|
||||
exit(1);
|
||||
}
|
||||
case MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING:
|
||||
break;
|
||||
case MSG_AGENT_TO_SERVER_STDOUT:
|
||||
case MSG_AGENT_TO_SERVER_STDERR:
|
||||
@ -451,8 +474,8 @@ void handle_message_from_agent()
|
||||
// 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_EXEC) {
|
||||
handle_execute_predefined_command(s_hdr.client_id);
|
||||
if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING) {
|
||||
handle_execute_predefined_command();
|
||||
return;
|
||||
}
|
||||
|
||||
|
135
qrexec/qrexec_policy
Executable file
135
qrexec/qrexec_policy
Executable file
@ -0,0 +1,135 @@
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import subprocess
|
||||
import xen.lowlevel.xl
|
||||
|
||||
POLICY_FILE_DIR="/etc/qubes_rpc/policy"
|
||||
QREXEC_CLIENT="/usr/lib/qubes/qrexec_client"
|
||||
|
||||
def line_to_dict(line):
|
||||
tokens=line.split()
|
||||
if len(tokens) < 3:
|
||||
return None
|
||||
dict={}
|
||||
dict['source']=tokens[0]
|
||||
dict['dest']=tokens[1]
|
||||
|
||||
action_list=tokens[2].split(',')
|
||||
dict['action']=action_list.pop(0)
|
||||
|
||||
for iter in action_list:
|
||||
paramval=iter.split("=")
|
||||
dict["action."+paramval[0]]=paramval[1]
|
||||
|
||||
return dict
|
||||
|
||||
|
||||
def read_policy_file(exec_index):
|
||||
policy_file=POLICY_FILE_DIR+"/"+exec_index
|
||||
if not os.path.isfile(policy_file):
|
||||
return None
|
||||
policy_list=list()
|
||||
f = open(policy_file)
|
||||
for iter in f.readlines():
|
||||
dict = line_to_dict(iter)
|
||||
if dict is not None:
|
||||
policy_list.append(dict)
|
||||
f.close()
|
||||
return policy_list
|
||||
|
||||
def is_match(item, config_term):
|
||||
return (item is not "dom0" and config_term == "anyvm") or item == config_term
|
||||
|
||||
def get_default_policy():
|
||||
dict={}
|
||||
dict["action"]="deny"
|
||||
return dict
|
||||
|
||||
|
||||
def find_policy(policy, domain, target):
|
||||
for iter in policy:
|
||||
if not is_match(domain, iter["source"]):
|
||||
continue
|
||||
if not is_match(target, iter["dest"]):
|
||||
continue
|
||||
return iter
|
||||
return get_default_policy()
|
||||
|
||||
def is_domain_running(target):
|
||||
xl_ctx = xen.lowlevel.xl.ctx()
|
||||
domains = xl_ctx.list_domains()
|
||||
for dominfo in domains:
|
||||
domname = xl_ctx.domid_to_name(dominfo.domid)
|
||||
if domname == target:
|
||||
return True
|
||||
return False
|
||||
|
||||
def spawn_target_if_necessary(target):
|
||||
if is_domain_running(target):
|
||||
return
|
||||
null=open("/dev/null", "r+")
|
||||
subprocess.call(["qvm-run", "-a", "-q", target, "true"], stdin=null, stdout=null)
|
||||
null.close()
|
||||
|
||||
def do_execute(domain, target, user, exec_index, process_ident):
|
||||
if target == "dom0":
|
||||
cmd="/usr/lib/qubes/qubes_rpc_multiplexer "+exec_index + " " + domain
|
||||
elif target == "dispvm":
|
||||
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + exec_index + " " + domain + " " +user
|
||||
else:
|
||||
# see the previous commit why "qvm-run -a" is broken and dangerous
|
||||
# also, dangling "xl" would keep stderr open and may prevent closing connection
|
||||
spawn_target_if_necessary(target)
|
||||
cmd= QREXEC_CLIENT + " -d " + target + " '" + user
|
||||
cmd+=":/usr/lib/qubes/qubes_rpc_multiplexer "+ exec_index + " " + domain + "'"
|
||||
os.execl(QREXEC_CLIENT, "qrexec_client", "-d", domain, "-l", cmd, "-c", process_ident)
|
||||
|
||||
def confirm_execution(domain, target, exec_index):
|
||||
text = "Do you allow domain \"" +domain + "\" to execute " + exec_index
|
||||
text+= " operation on the domain \"" + target +"\"?"
|
||||
retcode = subprocess.call(["/usr/bin/zenity", "--question", "--text", text])
|
||||
return retcode==0
|
||||
|
||||
def policy_editor(domain, target, exec_index):
|
||||
text = "Policy editor not yet implemented. Please add a line in the form \""
|
||||
text+= domain + " " + target + "action_to_take\""
|
||||
text+= " to /etc/qubes_rpc/policy/" + exec_index +" file in dom0, then close this info."
|
||||
subprocess.call(["/usr/bin/zenity", "--info", "--text", text])
|
||||
|
||||
def main():
|
||||
domain=sys.argv[1]
|
||||
target=sys.argv[2]
|
||||
exec_index=sys.argv[3]
|
||||
process_ident=sys.argv[4]
|
||||
|
||||
policy_list=read_policy_file(exec_index)
|
||||
if policy_list==None:
|
||||
policy_editor(domain, target, exec_index)
|
||||
policy_list=read_policy_file(exec_index)
|
||||
if policy_list==None:
|
||||
policy_list=list()
|
||||
|
||||
policy_dict=find_policy(policy_list, domain, target)
|
||||
|
||||
if policy_dict["action"] == "ask":
|
||||
if confirm_execution(domain, target, exec_index):
|
||||
policy_dict["action"] = "allow"
|
||||
else:
|
||||
policy_dict["action"] = "deny"
|
||||
|
||||
if policy_dict["action"] == "allow":
|
||||
if policy_dict.has_key("action.target"):
|
||||
target=policy_dict["action.target"]
|
||||
if policy_dict.has_key("action.user"):
|
||||
user=policy_dict["action.user"]
|
||||
else:
|
||||
user="user"
|
||||
do_execute(domain, target, user, exec_index, process_ident)
|
||||
|
||||
print >> sys.stderr, "Rpc denied:", domain, target, exec_index
|
||||
os.execl(QREXEC_CLIENT, "qrexec_client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
|
||||
|
||||
main()
|
||||
|
14
qrexec/qubes_rpc_multiplexer
Executable file
14
qrexec/qubes_rpc_multiplexer
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
QUBES_RPC=/etc/qubes_rpc
|
||||
if ! [ $# = 2 ] ; then
|
||||
echo $0: bad argument count >&2
|
||||
exit 1
|
||||
fi
|
||||
CFG_FILE=$QUBES_RPC/"$1"
|
||||
if [ -s "$CFG_FILE" ] ; then
|
||||
exec $(cat "$CFG_FILE") "$2"
|
||||
echo "$0: failed to execute handler for" "$1" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$0: nonexistent or empty" "$CFG_FILE" file >&2
|
||||
exit 1
|
@ -27,20 +27,12 @@
|
||||
#include <stdlib.h>
|
||||
#include "qrexec.h"
|
||||
|
||||
int get_server_socket(int domid, char *domname)
|
||||
int get_server_socket(char *socket_address)
|
||||
{
|
||||
struct sockaddr_un sockname;
|
||||
int s;
|
||||
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(socket_address);
|
||||
unlink(link_to_socket_name);
|
||||
symlink(socket_address, link_to_socket_name);
|
||||
|
||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
memset(&sockname, 0, sizeof(sockname));
|
||||
|
@ -75,17 +75,22 @@ cp qubes_core_appvm $RPM_BUILD_ROOT/etc/init.d/
|
||||
mkdir -p $RPM_BUILD_ROOT/var/lib/qubes
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/bin
|
||||
cp qubes_timestamp qvm-open-in-dvm2 $RPM_BUILD_ROOT/usr/bin
|
||||
cp qvm-open-in-vm $RPM_BUILD_ROOT/usr/bin
|
||||
cp qvm-copy-to-vm $RPM_BUILD_ROOT/usr/bin
|
||||
mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp qvm-copy-to-vm2.kde $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp qvm-copy-to-vm2.gnome $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp qvm-trigger-copy-to-vm $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp ../qrexec/qrexec_agent $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp dvm_file_editor qfile-agent qfile-agent-dvm qfile-unpacker $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp ../qrexec/qrexec_client_vm $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp ../qrexec/qubes_rpc_multiplexer $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp vm-file-editor qfile-agent qopen-in-vm qfile-unpacker $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir}
|
||||
cp qvm-copy.desktop qvm-dvm.desktop $RPM_BUILD_ROOT/%{kde_service_dir}
|
||||
mkdir -p $RPM_BUILD_ROOT/mnt/removable
|
||||
mkdir -p $RPM_BUILD_ROOT/etc/qubes_rpc
|
||||
cp qubes.Filecopy $RPM_BUILD_ROOT/etc/qubes_rpc
|
||||
cp qubes.OpenInVM $RPM_BUILD_ROOT/etc/qubes_rpc
|
||||
|
||||
mkdir -p $RPM_BUILD_ROOT/etc/X11
|
||||
cp xorg-preload-apps.conf $RPM_BUILD_ROOT/etc/X11
|
||||
@ -136,16 +141,21 @@ rm -rf $RPM_BUILD_ROOT
|
||||
/usr/lib/qubes/qvm-copy-to-vm2.kde
|
||||
/usr/lib/qubes/qvm-copy-to-vm2.gnome
|
||||
/usr/bin/qvm-open-in-dvm2
|
||||
/usr/bin/qvm-open-in-vm
|
||||
/usr/lib/qubes/meminfo-writer
|
||||
/usr/lib/qubes/dvm_file_editor
|
||||
/usr/lib/qubes/vm-file-editor
|
||||
%{kde_service_dir}/qvm-copy.desktop
|
||||
%{kde_service_dir}/qvm-dvm.desktop
|
||||
/usr/lib/qubes/qvm-trigger-copy-to-vm
|
||||
/usr/lib/qubes/qrexec_agent
|
||||
/usr/lib/qubes/qrexec_client_vm
|
||||
/usr/lib/qubes/qubes_rpc_multiplexer
|
||||
/usr/lib/qubes/qfile-agent
|
||||
/usr/lib/qubes/qfile-agent-dvm
|
||||
/usr/lib/qubes/qopen-in-vm
|
||||
/usr/lib/qubes/qfile-unpacker
|
||||
%dir /mnt/removable
|
||||
%dir /etc/qubes_rpc
|
||||
/etc/qubes_rpc/qubes.Filecopy
|
||||
/etc/qubes_rpc/qubes.OpenInVM
|
||||
/usr/bin/qubes_timestamp
|
||||
%dir /home_volatile
|
||||
%attr(700,user,user) /home_volatile/user
|
||||
|
@ -96,14 +96,24 @@ cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py
|
||||
cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
cp ../qrexec/qrexec_daemon $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
cp ../qrexec/qrexec_client $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
cp ../qrexec/qrexec_policy $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
cp aux-tools/qfile-dom0-unpacker $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
cp aux-tools/qubes-receive-updates $RPM_BUILD_ROOT/usr/lib/qubes/
|
||||
|
||||
mkdir -p $RPM_BUILD_ROOT/etc/qubes_rpc/policy
|
||||
cp ../appvm/qubes.Filecopy.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.Filecopy
|
||||
cp ../appvm/qubes.OpenInVM.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.OpenInVM
|
||||
cp qubes.SyncAppMenus.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.SyncAppMenus
|
||||
cp qubes.SyncAppMenus $RPM_BUILD_ROOT/etc/qubes_rpc/
|
||||
cp ../qrexec/qubes_rpc_multiplexer $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp aux-tools/qubes.ReceiveUpdates.policy $RPM_BUILD_ROOT/etc/qubes_rpc/policy/qubes.ReceiveUpdates
|
||||
cp aux-tools/qubes.ReceiveUpdates $RPM_BUILD_ROOT/etc/qubes_rpc/
|
||||
|
||||
cp restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin
|
||||
cp restore/xenstore-watch $RPM_BUILD_ROOT/usr/bin/xenstore-watch-qubes
|
||||
cp restore/qubes_restore restore/xenfreepages $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp restore/qubes_prepare_saved_domain.sh $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp restore/qfile-daemon-dvm $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
cp restore/qfile-daemon $RPM_BUILD_ROOT/usr/lib/qubes
|
||||
|
||||
mkdir -p $RPM_BUILD_ROOT/etc/yum.real.repos.d
|
||||
cp qubes-cached.repo $RPM_BUILD_ROOT/etc/yum.real.repos.d/
|
||||
@ -289,8 +299,8 @@ fi
|
||||
/usr/lib/qubes/qmemman_daemon.py*
|
||||
/usr/lib/qubes/meminfo-writer
|
||||
/usr/lib/qubes/qfile-daemon-dvm*
|
||||
/usr/lib/qubes/qfile-daemon
|
||||
/usr/lib/qubes/sync_rpmdb_updatevm.sh
|
||||
/usr/lib/qubes/qubes-receive-updates
|
||||
%attr(4750,root,qubes) /usr/lib/qubes/qfile-dom0-unpacker
|
||||
%attr(770,root,qubes) %dir /var/lib/qubes
|
||||
%attr(770,root,qubes) %dir /var/lib/qubes/vm-templates
|
||||
@ -321,6 +331,15 @@ fi
|
||||
/etc/xen/scripts/block-origin
|
||||
/etc/xen/scripts/vif-route-qubes
|
||||
/usr/lib/qubes/qrexec_client
|
||||
/usr/lib/qubes/qubes_rpc_multiplexer
|
||||
/usr/lib/qubes/qrexec_policy
|
||||
%dir /etc/qubes_rpc/policy
|
||||
/etc/qubes_rpc/policy/qubes.Filecopy
|
||||
/etc/qubes_rpc/policy/qubes.OpenInVM
|
||||
/etc/qubes_rpc/policy/qubes.SyncAppMenus
|
||||
/etc/qubes_rpc/policy//qubes.ReceiveUpdates
|
||||
/etc/qubes_rpc/qubes.SyncAppMenus
|
||||
/etc/qubes_rpc/qubes.ReceiveUpdates
|
||||
%attr(4750,root,qubes) /usr/lib/qubes/qrexec_daemon
|
||||
%attr(4750,root,qubes) /usr/lib/qubes/xenfreepages
|
||||
%attr(2770,root,qubes) %dir /var/log/qubes
|
||||
|
Loading…
Reference in New Issue
Block a user