diff --git a/appvm/Makefile b/appvm/Makefile index d1e1040f..42bb6369 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -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 *~ diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c index 530bbb2d..4b37738f 100644 --- a/appvm/qfile-agent.c +++ b/appvm/qfile-agent.c @@ -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; } diff --git a/appvm/qfile-agent-dvm.c b/appvm/qopen-in-vm.c similarity index 68% rename from appvm/qfile-agent-dvm.c rename to appvm/qopen-in-vm.c index 5db430cc..80f99c31 100644 --- a/appvm/qfile-agent-dvm.c +++ b/appvm/qopen-in-vm.c @@ -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; -} +} diff --git a/appvm/qubes.Filecopy b/appvm/qubes.Filecopy new file mode 100644 index 00000000..d82fa220 --- /dev/null +++ b/appvm/qubes.Filecopy @@ -0,0 +1 @@ +/usr/lib/qubes/qfile-unpacker diff --git a/appvm/qubes.Filecopy.policy b/appvm/qubes.Filecopy.policy new file mode 100644 index 00000000..39296a11 --- /dev/null +++ b/appvm/qubes.Filecopy.policy @@ -0,0 +1 @@ +anyvm anyvm ask,user=root diff --git a/appvm/qubes.OpenInVM b/appvm/qubes.OpenInVM new file mode 100644 index 00000000..48db9065 --- /dev/null +++ b/appvm/qubes.OpenInVM @@ -0,0 +1 @@ +/usr/lib/qubes/vm-file-editor diff --git a/appvm/qubes.OpenInVM.policy b/appvm/qubes.OpenInVM.policy new file mode 100644 index 00000000..e103d394 --- /dev/null +++ b/appvm/qubes.OpenInVM.policy @@ -0,0 +1,2 @@ +anyvm dispvm allow +anyvm anyvm ask diff --git a/appvm/qvm-copy-to-vm b/appvm/qvm-copy-to-vm index eaf8c8f7..26ee8769 100755 --- a/appvm/qvm-copy-to-vm +++ b/appvm/qvm-copy-to-vm @@ -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 "$@" diff --git a/appvm/qvm-copy-to-vm2.gnome b/appvm/qvm-copy-to-vm2.gnome index 8fabd2d0..cb2b86e5 100755 --- a/appvm/qvm-copy-to-vm2.gnome +++ b/appvm/qvm-copy-to-vm2.gnome @@ -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 diff --git a/appvm/qvm-copy-to-vm2.kde b/appvm/qvm-copy-to-vm2.kde index 7600bb56..5c56df08 100755 --- a/appvm/qvm-copy-to-vm2.kde +++ b/appvm/qvm-copy-to-vm2.kde @@ -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' diff --git a/appvm/qvm-open-in-dvm2 b/appvm/qvm-open-in-dvm2 index eb0d4e2e..dcc7195e 100755 --- a/appvm/qvm-open-in-dvm2 +++ b/appvm/qvm-open-in-dvm2 @@ -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" diff --git a/appvm/qvm-trigger-copy-to-vm b/appvm/qvm-open-in-vm similarity index 61% rename from appvm/qvm-trigger-copy-to-vm rename to appvm/qvm-open-in-vm index 56dcdef8..ffd087ea 100755 --- a/appvm/qvm-trigger-copy-to-vm +++ b/appvm/qvm-open-in-vm @@ -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" diff --git a/appvm/dvm_file_editor.c b/appvm/vm-file-editor.c similarity index 100% rename from appvm/dvm_file_editor.c rename to appvm/vm-file-editor.c diff --git a/common/qubes_download_dom0_updates.sh b/common/qubes_download_dom0_updates.sh index cb771a38..df67b92c 100755 --- a/common/qubes_download_dom0_updates.sh +++ b/common/qubes_download_dom0_updates.sh @@ -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 diff --git a/common/qubes_trigger_sync_appmenus.sh b/common/qubes_trigger_sync_appmenus.sh index fc5301a4..5390c2d2 100755 --- a/common/qubes_trigger_sync_appmenus.sh +++ b/common/qubes_trigger_sync_appmenus.sh @@ -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 diff --git a/dom0/restore/qfile-daemon b/dom0/aux-tools/qubes-receive-updates similarity index 68% rename from dom0/restore/qfile-daemon rename to dom0/aux-tools/qubes-receive-updates index aca6f25f..614a11ff 100755 --- a/dom0/restore/qfile-daemon +++ b/dom0/aux-tools/qubes-receive-updates @@ -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() diff --git a/dom0/aux-tools/qubes.ReceiveUpdates b/dom0/aux-tools/qubes.ReceiveUpdates new file mode 100644 index 00000000..71343239 --- /dev/null +++ b/dom0/aux-tools/qubes.ReceiveUpdates @@ -0,0 +1 @@ +/usr/lib/qubes/qubes-receive-updates diff --git a/dom0/aux-tools/qubes.ReceiveUpdates.policy b/dom0/aux-tools/qubes.ReceiveUpdates.policy new file mode 100644 index 00000000..74f80450 --- /dev/null +++ b/dom0/aux-tools/qubes.ReceiveUpdates.policy @@ -0,0 +1 @@ +anyvm dom0 allow diff --git a/dom0/qubes.SyncAppMenus b/dom0/qubes.SyncAppMenus new file mode 100644 index 00000000..461fd519 --- /dev/null +++ b/dom0/qubes.SyncAppMenus @@ -0,0 +1 @@ +/usr/bin/qvm-sync-appmenus diff --git a/dom0/qubes.SyncAppMenus.policy b/dom0/qubes.SyncAppMenus.policy new file mode 100644 index 00000000..74f80450 --- /dev/null +++ b/dom0/qubes.SyncAppMenus.policy @@ -0,0 +1 @@ +anyvm dom0 allow diff --git a/dom0/restore/qfile-daemon-dvm b/dom0/restore/qfile-daemon-dvm index 024c7eef..1a9fd8ff 100755 --- a/dom0/restore/qfile-daemon-dvm +++ b/dom0/restore/qfile-daemon-dvm @@ -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) diff --git a/qrexec/Makefile b/qrexec/Makefile index fc2c8fdf..f8f6124b 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -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 diff --git a/qrexec/README.rpc b/qrexec/README.rpc new file mode 100644 index 00000000..63af3005 --- /dev/null +++ b/qrexec/README.rpc @@ -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. + diff --git a/qrexec/glue.h b/qrexec/glue.h index 67fce180..0d92474c 100644 --- a/qrexec/glue.h +++ b/qrexec/glue.h @@ -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 { diff --git a/qrexec/qrexec.h b/qrexec/qrexec.h index bd996c48..a0f67565 100644 --- a/qrexec/qrexec.h +++ b/qrexec/qrexec.h @@ -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; +}; diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c index 06a89103..c1c6193c 100644 --- a/qrexec/qrexec_agent.c +++ b/qrexec/qrexec_agent.c @@ -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); diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c index c378a550..4065e6b0 100644 --- a/qrexec/qrexec_client.c +++ b/qrexec/qrexec_client.c @@ -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; diff --git a/qrexec/qrexec_client_vm.c b/qrexec/qrexec_client_vm.c new file mode 100644 index 00000000..4d0ae560 --- /dev/null +++ b/qrexec/qrexec_client_vm.c @@ -0,0 +1,109 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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 +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c index dafce73c..d2a777b4 100644 --- a/qrexec/qrexec_daemon.c +++ b/qrexec/qrexec_daemon.c @@ -29,6 +29,7 @@ #include #include #include +#include #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; } diff --git a/qrexec/qrexec_policy b/qrexec/qrexec_policy new file mode 100755 index 00000000..8d66630d --- /dev/null +++ b/qrexec/qrexec_policy @@ -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() + \ No newline at end of file diff --git a/qrexec/qubes_rpc_multiplexer b/qrexec/qubes_rpc_multiplexer new file mode 100755 index 00000000..8f03137a --- /dev/null +++ b/qrexec/qubes_rpc_multiplexer @@ -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 diff --git a/qrexec/unix_server.c b/qrexec/unix_server.c index 14a61273..c002784c 100644 --- a/qrexec/unix_server.c +++ b/qrexec/unix_server.c @@ -27,20 +27,12 @@ #include #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)); diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index 60218622..ce783b2c 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -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 diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index e5a74469..e95acc4a 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -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