Răsfoiți Sursa

Merge pull request #1 from QubesOS/master

merge update
awokd 6 ani în urmă
părinte
comite
c4c28605c9
51 a modificat fișierele cu 1013 adăugiri și 621 ștergeri
  1. 1 0
      .gitignore
  2. 2 2
      .travis.yml
  3. 17 17
      Makefile
  4. 10 0
      archlinux/PKGBUILD.install
  5. 136 0
      debian/changelog
  6. 2 0
      debian/control
  7. 2 2
      debian/qubes-core-agent.install
  8. 11 1
      debian/qubes-core-agent.postinst
  9. 7 1
      doc/vm-tools/qrexec-client-vm.rst
  10. 3 9
      init/functions
  11. 1 1
      misc/Makefile
  12. 1 1
      misc/qubes-desktop-run
  13. 4 1
      misc/qubes-download-dom0-updates.sh
  14. 4 4
      misc/qubes-r4.repo
  15. 11 0
      misc/qubes-run-terminal
  16. 5 0
      misc/qubes-run-terminal.desktop
  17. 2 2
      misc/qubes-session-autostart
  18. 14 1
      misc/uca_qubes.xml
  19. 11 0
      network/network-manager-prepare-conf-dir
  20. 7 1
      network/qubes-iptables
  21. 3 0
      network/setup-ip
  22. 6 1
      network/vif-route-qubes
  23. 14 6
      qrexec/qrexec-agent-data.c
  24. 1 1
      qrexec/qrexec-agent.c
  25. 2 1
      qrexec/qrexec-agent.h
  26. 42 16
      qrexec/qrexec-client-vm.c
  27. 5 2
      qrexec/qrexec-fork-server.c
  28. 0 1
      qubes-rpc/.gitignore
  29. 1 0
      qubes-rpc/dvm2.h
  30. 34 34
      qubes-rpc/gui-fatal.c
  31. 81 81
      qubes-rpc/qfile-agent.c
  32. 68 68
      qubes-rpc/qfile-unpacker.c
  33. 97 68
      qubes-rpc/qopen-in-vm.c
  34. 6 0
      qubes-rpc/qvm-actions.sh
  35. 7 2
      qubes-rpc/qvm-dvm.desktop
  36. 3 3
      qubes-rpc/qvm-open-in-dvm
  37. 27 6
      qubes-rpc/qvm-open-in-vm
  38. 6 8
      qubes-rpc/qvm_copy_nautilus.py
  39. 20 6
      qubes-rpc/qvm_dvm_nautilus.py
  40. 6 8
      qubes-rpc/qvm_move_nautilus.py
  41. 10 7
      qubes-rpc/tar2qfile.c
  42. 189 179
      qubes-rpc/vm-file-editor.c
  43. 46 6
      qubesagent/firewall.py
  44. 33 18
      qubesagent/test_firewall.py
  45. 17 4
      qubesagent/xdg.py
  46. 33 49
      rpm_spec/core-agent.spec.in
  47. 1 1
      run-tests
  48. 1 1
      version
  49. 1 0
      vm-systemd/75-qubes-vm.preset
  50. 1 0
      vm-systemd/qubes-firewall.service
  51. 1 1
      vm-systemd/qubes-misc-post.service

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@ deb/*
 *.o
 .coverage
 *.egg-info
+__pycache__

+ 2 - 2
.travis.yml

@@ -5,11 +5,11 @@ python: '3.5'
 install: git clone https://github.com/QubesOS/qubes-builder ~/qubes-builder
 script: ~/qubes-builder/scripts/travis-build
 env:
- - DISTS_VM=fc24 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
- - DISTS_VM=fc25 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
  - DISTS_VM=fc26 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
+ - DISTS_VM=fc27 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
  - DISTS_VM=jessie USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
  - DISTS_VM=stretch USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
+ - DISTS_VM=centos7 USE_QUBES_REPO_VERSION=4.0 USE_QUBES_REPO_TESTING=1
 
 jobs:
   include:

+ 17 - 17
Makefile

@@ -5,15 +5,16 @@ VERSION := $(shell cat version)
 DIST ?= fc18
 KDESERVICEDIR ?= /usr/share/kde4/services
 KDE5SERVICEDIR ?= /usr/share/kservices5/ServiceMenus/
+APPLICATIONSDIR ?= /usr/share/applications
 SBINDIR ?= /usr/sbin
 BINDIR ?= /usr/bin
 LIBDIR ?= /usr/lib
 SYSLIBDIR ?= /lib
 
 PYTHON ?= /usr/bin/python2
-PYTHON_SITEARCH = `python2 -c 'import distutils.sysconfig; print distutils.sysconfig.get_python_lib(1)'`
-PYTHON2_SITELIB = `python2 -c 'import distutils.sysconfig; print distutils.sysconfig.get_python_lib()'`
-PYTHON3_SITELIB = `python3 -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())'`
+PYTHON_SITEARCH = $(shell python2 -c 'import distutils.sysconfig; print distutils.sysconfig.get_python_lib(1)')
+PYTHON2_SITELIB = $(shell python2 -c 'import distutils.sysconfig; print distutils.sysconfig.get_python_lib()')
+PYTHON3_SITELIB = $(shell python3 -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())')
 
 # This makefile uses some bash-isms, make uses /bin/sh by default.
 SHELL = /bin/bash
@@ -46,6 +47,12 @@ clean:
 	make -C misc clean
 	make -C qrexec clean
 	make -C qubes-rpc clean
+	make -C doc clean
+	rm -rf qubesagent/*.pyc qubesagent/__pycache__
+	rm -rf test-packages/__pycache__
+	rm -rf test-packages/qubesagent.egg-info
+	rm -rf __pycache__
+	rm -f .coverage
 
 all:
 	make -C misc
@@ -77,6 +84,10 @@ ifeq ($(shell lsb_release -is), Ubuntu)
     SYSTEM_DROPINS := $(strip $(patsubst crond.service, cron.service, $(SYSTEM_DROPINS)))
     SYSTEM_DROPINS += anacron.service
     SYSTEM_DROPINS += anacron-resume.service
+    SYSTEM_DROPINS += netfilter-persistent.service
+    SYSTEM_DROPINS += exim4.service
+    SYSTEM_DROPINS += avahi-daemon.service
+
 endif
 
 # Debian Dropins
@@ -208,7 +219,7 @@ install-common: install-doc
 		misc/20_org.mate.NotificationDaemon.qubes.gschema.override \
 		misc/20_org.gnome.desktop.wm.preferences.qubes.gschema.override \
 		$(DESTDIR)/usr/share/glib-2.0/schemas/
-	install -g user -m 2775 -d $(DESTDIR)/var/lib/qubes/dom0-updates
+	install -m 2775 -d $(DESTDIR)/var/lib/qubes/dom0-updates
 	install -D -m 0644 misc/qubes-master-key.asc $(DESTDIR)/usr/share/qubes/qubes-master-key.asc
 	install misc/resize-rootfs $(DESTDIR)$(LIBDIR)/qubes/
 
@@ -223,6 +234,8 @@ install-common: install-doc
 	install -d $(DESTDIR)$(BINDIR)
 	install -m 0755 misc/qubes-session-autostart $(DESTDIR)$(BINDIR)/qubes-session-autostart
 	install -m 0755 misc/qvm-features-request $(DESTDIR)$(BINDIR)/qvm-features-request
+	install -m 0755 misc/qubes-run-terminal $(DESTDIR)/$(BINDIR)
+	install -D -m 0755 misc/qubes-run-terminal.desktop $(DESTDIR)/$(APPLICATIONSDIR)/qubes-run-terminal.desktop
 	install -m 0755 qubes-rpc/qvm-sync-clock $(DESTDIR)$(BINDIR)/qvm-sync-clock
 	install qubes-rpc/{qvm-open-in-dvm,qvm-open-in-vm,qvm-copy-to-vm,qvm-run-vm} $(DESTDIR)/usr/bin
 	install qubes-rpc/qvm-copy $(DESTDIR)/usr/bin
@@ -287,23 +300,10 @@ install-common: install-doc
 	install -d $(DESTDIR)/usr/share/nautilus-python/extensions
 	install -m 0644 qubes-rpc/*_nautilus.py $(DESTDIR)/usr/share/nautilus-python/extensions
 
-ifeq ($(findstring CentOS,$(shell cat /etc/redhat-release)),)
-	install -D -m 0644 misc/dconf-profile-user $(DESTDIR)/etc/dconf/profile/user
 	install -D -m 0644 misc/dconf-db-local-dpi $(DESTDIR)/etc/dconf/db/local.d/dpi
-endif
 
 	install -D -m 0755 misc/qubes-desktop-run $(DESTDIR)$(BINDIR)/qubes-desktop-run
 
-	mkdir -p $(DESTDIR)/$(PYTHON_SITEARCH)/qubes/
-
-ifeq ($(shell lsb_release -is), Debian)
-	install -m 0644 misc/qubesxdg.py $(DESTDIR)/$(PYTHON2_SITELIB)/
-else ifeq ($(shell lsb_release -is), Ubuntu)
-	install -m 0644 misc/qubesxdg.py $(DESTDIR)/$(PYTHON2_SITELIB)/
-else
-	install -m 0644 misc/py2/qubesxdg.py* $(DESTDIR)/$(PYTHON2_SITELIB)/
-endif
-
 	install -d $(DESTDIR)/mnt/removable
 
 	install -D -m 0644 misc/xorg-preload-apps.conf $(DESTDIR)/etc/X11/xorg-preload-apps.conf

+ 10 - 0
archlinux/PKGBUILD.install

@@ -82,6 +82,14 @@ update_qubesconfig() {
     #/usr/lib/qubes/update-proxy-configs
     # Archlinux pacman configuration is handled in update_finalize
 
+    if ! [ -r /etc/dconf/profile/user ]; then
+        mkdir -p /etc/dconf/profile
+        echo "user-db:user" >> /etc/dconf/profile/user
+        echo "system-db:local" >> /etc/dconf/profile/user
+    fi
+
+    dconf update &> /dev/null || :
+
     # Location of files which contains list of protected files
     mkdir -p /etc/qubes/protected-files.d
     # shellcheck source=init/functions
@@ -324,6 +332,8 @@ post_install() {
         cp /etc/init/serial.conf /var/lib/qubes/serial.orig
     fi
 
+    chgrp user /var/lib/qubes/dom0-updates
+
     # Remove most of the udev scripts to speed up the VM boot time
     # Just leave the xen* scripts, that are needed if this VM was
     # ever used as a net backend (e.g. as a VPN domain in the future)

+ 136 - 0
debian/changelog

@@ -1,3 +1,139 @@
+qubes-core-agent (4.0.31-1) unstable; urgency=medium
+
+  * debian: add Depends: qubesdb-vm
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Fri, 15 Jun 2018 14:32:23 +0200
+
+qubes-core-agent (4.0.30-1) wheezy; urgency=medium
+
+  [ Christopher Laprise ]
+  * Fixes issue #3939
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Tue, 05 Jun 2018 01:39:04 +0200
+
+qubes-core-agent (4.0.29-1) unstable; urgency=medium
+
+  * Drop leftovers of qubes-netwatcher service
+  * qrexec: fix handling remote domain death
+  * network: use iptables-restore --wait if available
+  * rpm: add BR: systemd for pre/post install macros
+  * qubes-rpc: fix code style - indent with spaces
+  * qvm-open-in-vm: implement --view-only option
+  * qvm-open-in-vm: mark file as read-only if opened with --view-only
+  * Add file managers integration for qvm-open-in-dvm --view-only
+  * Add build-time assert for filename buffer size
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Tue, 29 May 2018 00:40:11 +0200
+
+qubes-core-agent (4.0.28-1) wheezy; urgency=medium
+
+  [ Peter Gerber ]
+  * Qubes firewall: correct syntax for icmpv6 rejects
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Thu, 10 May 2018 12:21:39 +0200
+
+qubes-core-agent (4.0.27-1) unstable; urgency=medium
+
+  [ X4lldux ]
+  * Move/Copy many files in one step via nautilus extension
+
+  [ Marek Marczykowski-Górecki ]
+  * centos: exclude only dconf user profile, keep dpi config
+  * travis: add centos7
+  * Fix packaging: 'user' group, BACKEND_VMM var
+  * Create /etc/dconf/profile/user dynamically, if not present
+  * Require dconf utility to (re)build /etc/dconf/db/local
+  * Fix make clean
+  * qubes-firewall: reject packets instead of dropping
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Wed, 02 May 2018 05:05:33 +0200
+
+qubes-core-agent (4.0.26-1) unstable; urgency=medium
+
+  * Change repository URLs to https
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Sun, 22 Apr 2018 00:29:02 +0200
+
+qubes-core-agent (4.0.25-1) unstable; urgency=medium
+
+  [ Marek Marczykowski-Górecki ]
+  * debian: don't call dconf if it isn't installed
+  * qrexec: add qrexec-client-vm --buffer-size option
+
+  [ unman ]
+  * Add missing services in Ubuntu templates.
+
+  [ Simon Gaiser ]
+  * qrexec-fork-server: Always initialize addrlen argument of accept()
+
+  [ Marek Marczykowski-Górecki ]
+  * qrexec: fix arguments handling
+  * Move 'qubesxdg' into qubesagent python package
+  * Fix shell calls in Makefile
+  * Fix waiting for application exit in qubesagent.xdg.launch
+  * Load only test_* files when looking for tests (python)
+  * qubes-session-autostart: do not wait for applications exit
+  * Do not start dkms.service
+  * network: do not assume IPv6 gateway is a link-local address
+  * qubes-firewall: handle only traffic originating from VMs
+  * network: make sure static NM configuration is created before NM
+    start
+
+  [ Davíð Steinn Geirsson ]
+  * Add misc/qubes-run-terminal to launch any available terminal
+    emulator
+
+  [ Frédéric Pierret ]
+  * Create .spec.in and Source0
+  * Remove _builddir
+  * spec.in: add changelog placeholder
+  * spec.in: fix %if expressions and remove useless conditions
+
+  [ Vladimir Lushnikov ]
+  * Problem: Unable to use pkg.install with Salt in dom0 when using
+    UpdateVM that has only yum due to incorrect options passed by Salt
+    assuming dnf presence
+
+  [ Marek Marczykowski-Górecki ]
+  * Use only /etc/skel to provision user's home directory of new VM
+  * Update gitignore and make clean target
+  * qubes-firewall: signal service readiness only after initial scripts
+
+  [ Frédéric Pierret ]
+  * Fix GCC8 warnings
+  * Add missing python-setuptools dependency
+  * Use %{python3_pkgversion} instead of duplicating python3 targets
+
+  [ Marek Marczykowski-Górecki ]
+  * travis: update Fedora versions
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Sat, 21 Apr 2018 15:10:20 +0200
+
+qubes-core-agent (4.0.24-1) unstable; urgency=medium
+
+  [ Marek Marczykowski-Górecki ]
+  * Drop Fedora < 22 support
+  * Call qubes.PostInstall service to notify dom0 about all
+    apps/features
+  * dom0-updates: refactor for ease adding new actions with old yum
+  * dom0-update: add some approximation of 'list', 'search' and
+    'reinstall'
+  * Drop fakeroot for list/search actions on Debian
+
+  [ Rusty Bird ]
+  * Really enable qubes-sync-time.timer
+
+  [ Frédéric Pierret ]
+  * centos: fix conflict with dconf
+
+  [ Marek Marczykowski-Górecki ]
+  * Speed up initial /rw setup
+
+  [ awokd ]
+  * reinstal -> reinstall
+
+ -- Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>  Tue, 27 Feb 2018 15:17:51 +0100
+
 qubes-core-agent (4.0.23-1) unstable; urgency=medium
 
   * qrexec: launch services in login shell

+ 2 - 0
debian/control

@@ -26,6 +26,7 @@ Vcs-Git: https://github.com/QubesOS/qubes-core-agent-linux
 Package: qubes-core-agent
 Architecture: any
 Depends:
+    dconf-cli,
     dmsetup,
     gawk,
     imagemagick,
@@ -45,6 +46,7 @@ Depends:
     python-dbus,
     qubes-utils (>= 3.1.3),
     qubes-core-agent-qrexec,
+    qubesdb-vm,
     systemd,
     x11-xserver-utils,
     xdg-user-dirs,

+ 2 - 2
debian/qubes-core-agent.install

@@ -3,7 +3,6 @@ etc/apt/apt.conf.d/00notify-hook
 etc/apt/apt.conf.d/70no-unattended
 etc/apt/sources.list.d/qubes-r4.list
 etc/apt/trusted.gpg.d/qubes-archive-keyring.gpg
-etc/dconf/profile/user
 etc/dconf/db/local.d/dpi
 etc/default/grub.d/30-qubes.cfg
 etc/fstab
@@ -92,6 +91,7 @@ lib/systemd/system/systemd-timesyncd.service.d/30_qubes.conf
 usr/bin/qubes-desktop-run
 usr/bin/qubes-open
 usr/bin/qubes-session-autostart
+usr/bin/qubes-run-terminal
 usr/bin/qvm-copy
 usr/bin/qvm-copy-to-vm
 usr/bin/qvm-features-request
@@ -102,7 +102,6 @@ usr/bin/qvm-open-in-vm
 usr/bin/qvm-run-vm
 usr/bin/qvm-sync-clock
 usr/bin/xenstore-watch-qubes
-usr/lib/python2.7/dist-packages/qubesxdg.py
 usr/lib/python2.7/dist-packages/qubesagent-*.egg-info/*
 usr/lib/python2.7/dist-packages/qubesagent/*
 usr/lib/qubes-bind-dirs.d/30_cron.conf
@@ -142,6 +141,7 @@ usr/lib/systemd/user/pulseaudio.socket.d/30_qubes.conf
 usr/share/glib-2.0/schemas/*
 usr/share/kde4/services/*.desktop
 usr/share/kservices5/ServiceMenus/*.desktop
+usr/share/applications/*.desktop
 usr/share/man/man1/qvm-*
 usr/share/qubes/mime-override/globs
 usr/share/qubes/qubes-master-key.asc

+ 11 - 1
debian/qubes-core-agent.postinst

@@ -145,6 +145,8 @@ case "${1}" in
         fi
         systemctl reenable haveged
 
+        chgrp user /var/lib/qubes/dom0-updates
+
         debug "UPDATE..."
         # disable some Upstart services
         for init in plymouth-shutdown \
@@ -179,7 +181,15 @@ case "${1}" in
 
         glib-compile-schemas /usr/share/glib-2.0/schemas || true
 
-        dconf update || true
+        if ! [ -r /etc/dconf/profile/user ]; then
+            mkdir -p /etc/dconf/profile
+            echo "user-db:user" >> /etc/dconf/profile/user
+            echo "system-db:local" >> /etc/dconf/profile/user
+        fi
+
+        if [ -x /usr/bin/dconf ]; then
+            dconf update
+        fi
 
         # tell dom0 about installed updates (applications, features etc)
         /etc/qubes-rpc/qubes.PostInstall || true

+ 7 - 1
doc/vm-tools/qrexec-client-vm.rst

@@ -8,7 +8,7 @@ qrexec-client-vm - call Qubes RPC service
 
 SYNOPSIS
 ========
-| qrexec-client-vm *target_vmname* *service* [*local_program* [*local program arguments*]]
+| qrexec-client-vm [--buffer-size=*BUFFER_SIZE*] *target_vmname* *service* [*local_program* [*local program arguments*]]
 
 DESCRIPTION
 ===========
@@ -27,6 +27,12 @@ stdin/stdout is connected to those of ``qrexec-client-vm``.
 OPTIONS
 =======
 
+--buffer-size=*BUFFER_SIZE*
+
+    Optional buffer size for vchan connection. This size is used as minimum
+    size for a buffer in each connection direction (read and write).
+    Default: 64KiB.
+
 *target_vmname*
 
     Name of target VM to which service is requested. Qubes RPC policy may

+ 3 - 9
init/functions

@@ -158,15 +158,9 @@ initialize_home() {
         homedir=$(echo "$pair" | awk -F : ' { print $4 } ')
         homedirwithouthome=${homedir#/home/}
         if ! test -d "$home_root/$homedirwithouthome" || [ "$mode" = "unconditionally" ] ; then
-            if [ "$homedir" == "/home/user" ] && [ -d "/home.orig/$homedirwithouthome" ] ; then
-                echo "initialize_home: populating $mode $home_root/$homedirwithouthome from /home.orig/$homedirwithouthome" >&2
-                mkdir -p "$home_root/$homedirwithouthome"
-                cp -af -T "/home.orig/$homedirwithouthome" "$home_root/$homedirwithouthome"
-            else
-                echo "initialize_home: populating $mode $home_root/$homedirwithouthome from /etc/skel" >&2
-                mkdir -p "$home_root/$homedirwithouthome"
-                cp -af -T /etc/skel "$home_root/$homedirwithouthome"
-            fi
+            echo "initialize_home: populating $mode $home_root/$homedirwithouthome from /etc/skel" >&2
+            mkdir -p "$home_root/$homedirwithouthome"
+            cp -af -T /etc/skel "$home_root/$homedirwithouthome"
             echo "initialize_home: adjusting permissions $mode on $home_root/$homedirwithouthome" >&2
             chown -R "$uid" "$home_root/$homedirwithouthome" &
             waitpids="$!"

+ 1 - 1
misc/Makefile

@@ -23,7 +23,7 @@ python2:
 python3:
 	rm -rf py3
 	mkdir -p py3
-	cp dnf-qubes-hooks.py qubesxdg.py py3/
+	cp dnf-qubes-hooks.py py3/
 	python3 -m compileall py3
 	python3 -O -m compileall py3
 

+ 1 - 1
misc/qubes-desktop-run

@@ -1,6 +1,6 @@
 #!/usr/bin/python
 
-from qubesxdg import launch
+from qubesagent.xdg import launch
 import sys
 
 if __name__ == '__main__':

+ 4 - 1
misc/qubes-download-dom0-updates.sh

@@ -53,6 +53,9 @@ fi
 YUM="yum"
 if type dnf >/dev/null 2>&1; then
     YUM="dnf --best --allowerasing --noplugins"
+else
+    # salt in dom0 thinks it's using dnf but we only have yum so need to remove extra options
+    OPTS="${OPTS/--best --allowerasing/}"
 fi
 
 if ! [ -d "$DOM0_UPDATES_DIR" ]; then
@@ -126,7 +129,7 @@ if ! $YUM --help | grep -q downloadonly; then
     elif [ "$YUM_ACTION" == "list" ] || [ "$YUM_ACTION" == "search" ]; then
         # those actions do not download any package, so lack of --downloadonly is irrelevant
         YUM_COMMAND="$YUM $YUM_ACTION -y"
-    elif [ "$YUM_ACTION" == "reinstal" ]; then
+    elif [ "$YUM_ACTION" == "reinstall" ]; then
         # this is just approximation of 'reinstall' action...
         # shellcheck disable=SC2086
         PKGLIST=$(rpm --root=$DOM0_UPDATES_DIR -q $PKGLIST)

+ 4 - 4
misc/qubes-r4.repo

@@ -1,6 +1,6 @@
 [qubes-vm-r4.0-current]
 name = Qubes OS Repository for VM (updates)
-baseurl = http://yum.qubes-os.org/r4.0/current/vm/fc$releasever
+baseurl = https://yum.qubes-os.org/r4.0/current/vm/fc$releasever
 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-4-primary
 skip_if_unavailable=False
 gpgcheck = 1
@@ -8,7 +8,7 @@ enabled=1
 
 [qubes-vm-r4.0-current-testing]
 name = Qubes OS Repository for VM (updates-testing)
-baseurl = http://yum.qubes-os.org/r4.0/current-testing/vm/fc$releasever
+baseurl = https://yum.qubes-os.org/r4.0/current-testing/vm/fc$releasever
 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-4-primary
 skip_if_unavailable=False
 gpgcheck = 1
@@ -16,7 +16,7 @@ enabled=0
 
 [qubes-vm-r4.0-security-testing]
 name = Qubes OS Repository for VM (security-testing)
-baseurl = http://yum.qubes-os.org/r4.0/security-testing/vm/fc$releasever
+baseurl = https://yum.qubes-os.org/r4.0/security-testing/vm/fc$releasever
 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-4-primary
 skip_if_unavailable=False
 gpgcheck = 1
@@ -24,7 +24,7 @@ enabled=0
 
 [qubes-vm-r4.0-unstable]
 name = Qubes OS Repository for VM (unstable)
-baseurl = http://yum.qubes-os.org/r4.0/unstable/vm/fc$releasever
+baseurl = https://yum.qubes-os.org/r4.0/unstable/vm/fc$releasever
 gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-qubes-4-unstable
 gpgcheck = 1
 enabled=0

+ 11 - 0
misc/qubes-run-terminal

@@ -0,0 +1,11 @@
+#!/bin/sh
+# Try to find a terminal emulator that's installed and run it.
+
+for terminal in x-terminal-emulator gnome-terminal xfce4-terminal konsole urxvt rxvt termit terminator Eterm aterm roxterm termite lxterminal mate-terminal terminology st xterm; do
+    if which $terminal >/dev/null 2>&1 ; then
+        exec "$terminal"
+    fi
+done
+
+echo "ERROR: No suitable terminal found." > /dev/stderr
+

+ 5 - 0
misc/qubes-run-terminal.desktop

@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=Run Terminal
+Exec=qubes-run-terminal
+Icon=utilities-terminal
+Type=Application

+ 2 - 2
misc/qubes-session-autostart

@@ -25,7 +25,7 @@ import subprocess
 import sys
 
 from xdg.DesktopEntry import DesktopEntry
-from qubesxdg import launch
+from qubesagent.xdg import launch
 import xdg.BaseDirectory
 import os
 
@@ -76,7 +76,7 @@ def process_autostart(environments):
                 else:
                     entry = DesktopEntry(entry_path)
                 if entry_should_be_started(entry, environments):
-                    launch(entry_path)
+                    launch(entry_path, wait=False)
             except Exception as e:
                 print >>sys.stderr, "Failed to process '{}': {}".format(
                     entry_name, str(e)

+ 14 - 1
misc/uca_qubes.xml

@@ -59,7 +59,7 @@
 </action>
 <action>
 	<icon>document-open</icon>
-	<name>Open in DisposableVM</name>
+	<name>Edit in DisposableVM</name>
 	<unique-id>1507455559234996-8</unique-id>
 	<command>/usr/lib/qubes/qvm-actions.sh opendvm %F</command>
 	<description></description>
@@ -70,3 +70,16 @@
 	<text-files/>
 	<video-files/>
 </action>
+<action>
+	<icon>document-open</icon>
+	<name>View in DisposableVM</name>
+	<unique-id>1507455559234997-9</unique-id>
+	<command>/usr/lib/qubes/qvm-actions.sh viewdvm %F</command>
+	<description></description>
+	<patterns>*</patterns>
+	<audio-files/>
+	<image-files/>
+	<other-files/>
+	<text-files/>
+	<video-files/>
+</action>

+ 11 - 0
network/network-manager-prepare-conf-dir

@@ -16,4 +16,15 @@ unmanaged_devices=mac:fe:ff:ff:ff:ff:ff
 sed -r -i -e "s/^#?unmanaged-devices=.*/unmanaged-devices=$unmanaged_devices/" /etc/NetworkManager/NetworkManager.conf
 sed -r -i -e "s/^#?plugins=.*/plugins=keyfile/" /etc/NetworkManager/NetworkManager.conf
 
+# setup uplink configuration if applicable - this needs to be done before
+# starting NetworkManager, otherwise it will try default DHCP configuration
+# first and only after a timeout fallback to static one - introducing delay in
+# network connectivity
+export INTERFACE=eth0
+if qubesdb-read /qubes-ip >/dev/null 2>/dev/null &&
+        [ -e /sys/class/net/$INTERFACE ] &&
+        [ ! -r /etc/NetworkManager/system-connections/qubes-uplink-$INTERFACE ]; then
+    /usr/lib/qubes/setup-ip
+fi
+
 exit 0

+ 7 - 1
network/qubes-iptables

@@ -42,9 +42,15 @@ start() {
     # Do not start if there is no config file.
     [ ! -f "$IPTABLES_DATA" ] && return 6
 
+    CMD_ARGS=
+    if "$CMD-restore" --help 2>&1 | grep -q wait=; then
+        # 'wait' must be last on command line if secs not specified
+        CMD_ARGS=--wait
+    fi
+
     echo -n $"${CMD}: Applying firewall rules: "
 
-    "$CMD-restore" "$IPTABLES_DATA"
+    "$CMD-restore" "$IPTABLES_DATA" $CMD_ARGS
     ret="$?"
     if [ "$ret" -eq 0 ]; then
         echo OK

+ 3 - 0
network/setup-ip

@@ -91,6 +91,9 @@ __EOF__
         fi
         /sbin/ifconfig "$INTERFACE" up
         /sbin/route add -host "$gateway" dev "$INTERFACE"
+        if [ -n "$gateway6" ] && ! echo "$gateway6" | grep -q "^fe80:"; then
+            /sbin/route -6 add "$gateway6/128" dev "$INTERFACE"
+        fi
         if ! qsvc disable-default-route ; then
             /sbin/route add default gw "$gateway"
             if [ -n "$gateway6" ]; then

+ 6 - 1
network/vif-route-qubes

@@ -40,10 +40,12 @@ if [ "${ip}" ]; then
     # IPs as seen by this VM
     netvm_ip="$ip4"
     netvm_gw_ip=$(qubesdb-read /qubes-netvm-gateway)
+    netvm_gw_ip6=$(qubesdb-read /qubes-netvm-gateway6 || :)
     netvm_dns1_ip=$(qubesdb-read /qubes-netvm-primary-dns)
     netvm_dns2_ip=$(qubesdb-read /qubes-netvm-secondary-dns)
 
     back_ip="$netvm_gw_ip"
+    back_ip6="$netvm_gw_ip6"
 
     # IPs as seen by the VM - if other than $netvm_ip
     appvm_gw_ip="$(qubesdb-read "/mapped-ip/$ip4/visible-gateway" 2>/dev/null || :)"
@@ -106,7 +108,10 @@ if [ "${ip}" ] ; then
         echo -e "*raw\n$iptables_cmd -i ${vif} -j DROP\nCOMMIT" | \
             ${cmdprefix} flock $lockfile ip6tables-restore --noflush
     fi
-	${cmdprefix} ip addr "${ipcmd}" "${back_ip}/32" dev "${vif}"
+    ${cmdprefix} ip addr "${ipcmd}" "${back_ip}/32" dev "${vif}"
+    if [ "${back_ip6}" ] && [[ "${back_ip6}" != "fe80:"* ]]; then
+        ${cmdprefix} ip addr "${ipcmd}" "${back_ip6}/128" dev "${vif}"
+    fi
 fi
 
 log debug "Successful vif-route-qubes $command for $vif."

+ 14 - 6
qrexec/qrexec-agent-data.c

@@ -295,6 +295,7 @@ int process_child_io(libvchan_t *data_vchan,
     int remote_process_status = -1;
     int ret, max_fd;
     struct timespec zero_timeout = { 0, 0 };
+    struct timespec normal_timeout = { 10, 0 };
     struct buffer stdin_buf;
 
     sigemptyset(&selectmask);
@@ -386,7 +387,7 @@ int process_child_io(libvchan_t *data_vchan,
             /* check for other FDs, but exit immediately */
             ret = pselect(max_fd + 1, &rdset, &wrset, NULL, &zero_timeout, &selectmask);
         } else
-            ret = pselect(max_fd + 1, &rdset, &wrset, NULL, NULL, &selectmask);
+            ret = pselect(max_fd + 1, &rdset, &wrset, NULL, &normal_timeout, &selectmask);
         if (ret < 0) {
             if (errno == EINTR)
                 continue;
@@ -490,10 +491,14 @@ int process_child_io(libvchan_t *data_vchan,
  *  MSG_EXEC_CMDLINE - connect to vchan server, fork+exec process given by
  *    cmdline parameter, pass the data to/from that process, then return local
  *    process exit code
+ *
+ *  buffer_size is about vchan buffer allocated (only for vchan server cases),
+ *  use 0 to use built-in default (64k); needs to be power of 2
  */
 int handle_new_process_common(int type, int connect_domain, int connect_port,
                 char *cmdline, int cmdline_len, /* MSG_JUST_EXEC and MSG_EXEC_CMDLINE */
-                int stdin_fd, int stdout_fd, int stderr_fd /* MSG_SERVICE_CONNECT */)
+                int stdin_fd, int stdout_fd, int stderr_fd /* MSG_SERVICE_CONNECT */,
+                int buffer_size)
 {
     libvchan_t *data_vchan;
     int exit_code = 0;
@@ -504,9 +509,12 @@ int handle_new_process_common(int type, int connect_domain, int connect_port,
         cmdline[cmdline_len-1] = 0;
     }
 
+    if (buffer_size == 0)
+        buffer_size = VCHAN_BUFFER_SIZE;
+
     if (type == MSG_SERVICE_CONNECT) {
         data_vchan = libvchan_server_init(connect_domain, connect_port,
-                VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
+                buffer_size, buffer_size);
         if (data_vchan)
             libvchan_wait(data_vchan);
     } else {
@@ -563,7 +571,7 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
     /* child process */
     exit_code = handle_new_process_common(type, connect_domain, connect_port,
             cmdline, cmdline_len,
-            -1, -1, -1);
+            -1, -1, -1, 0);
 
     exit(exit_code);
     /* suppress warning */
@@ -572,13 +580,13 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
 
 /* Returns exit code of remote process */
 int handle_data_client(int type, int connect_domain, int connect_port,
-                int stdin_fd, int stdout_fd, int stderr_fd)
+                int stdin_fd, int stdout_fd, int stderr_fd, int buffer_size)
 {
     int exit_code;
 
     assert(type == MSG_SERVICE_CONNECT);
 
     exit_code = handle_new_process_common(type, connect_domain, connect_port,
-            NULL, 0, stdin_fd, stdout_fd, stderr_fd);
+            NULL, 0, stdin_fd, stdout_fd, stderr_fd, buffer_size);
     return exit_code;
 }

+ 1 - 1
qrexec/qrexec-agent.c

@@ -382,7 +382,7 @@ int try_fork_server(int type, int connect_domain, int connect_port,
 
     remote.sun_family = AF_UNIX;
     strncpy(remote.sun_path, fork_server_socket_path,
-            sizeof(remote.sun_path));
+            sizeof(remote.sun_path) - 1);
     free(fork_server_socket_path);
 
     if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {

+ 2 - 1
qrexec/qrexec-agent.h

@@ -37,7 +37,8 @@ pid_t handle_new_process(int type,
         char *cmdline, int cmdline_len);
 int handle_data_client(int type,
         int connect_domain, int connect_port,
-        int stdin_fd, int stdout_fd, int stderr_fd);
+        int stdin_fd, int stdout_fd, int stderr_fd,
+        int buffer_size);
 
 
 struct qrexec_cmd_info {

+ 42 - 16
qrexec/qrexec-client-vm.c

@@ -27,6 +27,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
+#include <getopt.h>
 #include "libqrexec-utils.h"
 #include "qrexec.h"
 #include "qrexec-agent.h"
@@ -54,7 +55,7 @@ int connect_unix_socket(char *path)
 
     remote.sun_family = AF_UNIX;
     strncpy(remote.sun_path, path,
-            sizeof(remote.sun_path));
+            sizeof(remote.sun_path) - 1);
     len = strlen(remote.sun_path) + sizeof(remote.sun_family);
     if (connect(s, (struct sockaddr *) &remote, len) == -1) {
         perror("connect");
@@ -85,6 +86,19 @@ void convert_target_name_keyword(char *target)
             target[i] = '@';
 }
 
+struct option longopts[] = {
+    { "buffer-size", required_argument, 0,  'b' },
+    { NULL, 0, 0, 0},
+};
+
+_Noreturn void usage(const char *argv0) {
+    fprintf(stderr,
+            "usage: %s [--buffer-size=BUFFER_SIZE] target_vmname program_ident [local_program [local program arguments]]\n",
+            argv0);
+    fprintf(stderr, "BUFFER_SIZE is minimum vchan buffer size (default: 64k)\n");
+    exit(2);
+}
+
 int main(int argc, char **argv)
 {
     int trigger_fd;
@@ -95,25 +109,37 @@ int main(int argc, char **argv)
     char *abs_exec_path;
     pid_t child_pid = 0;
     int inpipe[2], outpipe[2];
+    int buffer_size = 0;
+    int opt;
+
+    while (1) {
+        opt = getopt_long(argc, argv, "+", longopts, NULL);
+        if (opt == -1)
+            break;
+        switch (opt) {
+            case 'b':
+                buffer_size = atoi(optarg);
+                break;
+            case '?':
+                usage(argv[0]);
+        }
+    }
 
-    if (argc < 3) {
-        fprintf(stderr,
-                "usage: %s target_vmname program_ident [local_program [local program arguments]]\n",
-                argv[0]);
-        exit(1);
+    if (argc - optind < 2) {
+        usage(argv[0]);
     }
-    if (argc > 3) {
+    if (argc - optind > 2) {
         start_local_process = 1;
     }
 
     trigger_fd = connect_unix_socket(QREXEC_AGENT_TRIGGER_PATH);
 
     memset(&params, 0, sizeof(params));
-    strncpy(params.service_name, argv[2], sizeof(params.service_name));
+    strncpy(params.service_name, argv[optind + 1], sizeof(params.service_name) - 1);
 
-    convert_target_name_keyword(argv[1]);
-    strncpy(params.target_domain, argv[1],
-            sizeof(params.target_domain));
+    convert_target_name_keyword(argv[optind]);
+    strncpy(params.target_domain, argv[optind],
+            sizeof(params.target_domain) - 1);
 
     snprintf(params.request_id.ident,
             sizeof(params.request_id.ident), "SOCKET");
@@ -164,9 +190,9 @@ int main(int argc, char **argv)
                 close(inpipe[0]);
                 close(outpipe[1]);
 
-                abs_exec_path = strdup(argv[3]);
-                argv[3] = get_program_name(argv[3]);
-                execv(abs_exec_path, argv + 3);
+                abs_exec_path = strdup(argv[optind + 2]);
+                argv[optind + 2] = get_program_name(argv[optind + 2]);
+                execv(abs_exec_path, argv + optind + 2);
                 perror("execv");
                 exit(-1);
         }
@@ -175,11 +201,11 @@ int main(int argc, char **argv)
 
         ret = handle_data_client(MSG_SERVICE_CONNECT,
                 exec_params.connect_domain, exec_params.connect_port,
-                inpipe[1], outpipe[0], -1);
+                inpipe[1], outpipe[0], -1, buffer_size);
     } else {
         ret = handle_data_client(MSG_SERVICE_CONNECT,
                 exec_params.connect_domain, exec_params.connect_port,
-                1, 0, -1);
+                1, 0, -1, buffer_size);
     }
 
     close(trigger_fd);

+ 5 - 2
qrexec/qrexec-fork-server.c

@@ -112,12 +112,15 @@ int main(int argc, char **argv) {
     signal(SIGCHLD, SIG_IGN);
     register_exec_func(do_exec);
 
-    while ((fd = accept(s, (struct sockaddr *) &peer, &addrlen)) >= 0) {
+    while (1) {
+        addrlen = sizeof(peer);
+        fd = accept(s, (struct sockaddr *) &peer, &addrlen);
+        if (fd < 0)
+            break;
         if (read_all(fd, &info, sizeof(info))) {
             handle_single_command(fd, &info);
         }
         close(fd);
-        addrlen = sizeof(peer);
     }
     close(s);
     unlink(socket_path);

+ 0 - 1
qubes-rpc/.gitignore

@@ -1,6 +1,5 @@
 qubes_add_pendrive_script
 qubes_penctl
-qvm-open-in-dvm
 dvm_file_editor
 qfile-agent
 qfile-agent-dvm

+ 1 - 0
qubes-rpc/dvm2.h

@@ -1,2 +1,3 @@
 #define DVM_FILENAME_SIZE 256
 #define DVM_SPOOL "/home/user/.dvmspool"
+#define DVM_VIEW_ONLY_PREFIX "view-only-"

+ 34 - 34
qubes-rpc/gui-fatal.c

@@ -10,58 +10,58 @@
 
 static void fix_display(void)
 {
-	setenv("DISPLAY", ":0", 1);
+    setenv("DISPLAY", ":0", 1);
 }
 
 static void produce_message(const char * type, const char *fmt, va_list args)
 {
-	char *dialog_msg;
-	char buf[1024];
-	(void) vsnprintf(buf, sizeof(buf), fmt, args);
-	if (asprintf(&dialog_msg, "%s: %s: %s (error type: %s)",
-		 program_invocation_short_name, type, buf, strerror(errno)) < 0) {
-		fprintf(stderr, "Failed to allocate memory for error message :(\n");
-		return;
-	}
-	fprintf(stderr, "%s\n", dialog_msg);
-	switch (fork()) {
-	case -1:
-		exit(1);	//what else
-	case 0:
-		if (geteuid() == 0)
-			if (setuid(getuid()) != 0)
-				perror("setuid failed, calling kdialog/zenity as root");
-		fix_display();
+    char *dialog_msg;
+    char buf[1024];
+    (void) vsnprintf(buf, sizeof(buf), fmt, args);
+    if (asprintf(&dialog_msg, "%s: %s: %s (error type: %s)",
+         program_invocation_short_name, type, buf, strerror(errno)) < 0) {
+        fprintf(stderr, "Failed to allocate memory for error message :(\n");
+        return;
+    }
+    fprintf(stderr, "%s\n", dialog_msg);
+    switch (fork()) {
+    case -1:
+        exit(1);    //what else
+    case 0:
+        if (geteuid() == 0)
+            if (setuid(getuid()) != 0)
+                perror("setuid failed, calling kdialog/zenity as root");
+        fix_display();
 #ifdef USE_KDIALOG
-		execlp("/usr/bin/kdialog", "kdialog", "--sorry", dialog_msg, NULL);
+        execlp("/usr/bin/kdialog", "kdialog", "--sorry", dialog_msg, NULL);
 #else
 
-		execlp("/usr/bin/zenity", "zenity", "--error",  "--text", dialog_msg, NULL);
+        execlp("/usr/bin/zenity", "zenity", "--error",  "--text", dialog_msg, NULL);
 #endif
-		exit(1);
-	default:;
-	}
-	free(dialog_msg);
+        exit(1);
+    default:;
+    }
+    free(dialog_msg);
 }
 
 void gui_fatal(const char *fmt, ...)
 {
-	va_list args;
-	va_start(args, fmt);
-	produce_message("Fatal error", fmt, args);
-	va_end(args);
-	exit(1);
+    va_list args;
+    va_start(args, fmt);
+    produce_message("Fatal error", fmt, args);
+    va_end(args);
+    exit(1);
 }
 
 void qfile_gui_fatal(const char *fmt, va_list args) {
-	produce_message("Fatal error", fmt, args);
+    produce_message("Fatal error", fmt, args);
     exit(1);
 }
 
 void gui_nonfatal(const char *fmt, ...)
 {
-	va_list args;
-	va_start(args, fmt);
-	produce_message("Information", fmt, args);
-	va_end(args);
+    va_list args;
+    va_start(args, fmt);
+    produce_message("Information", fmt, args);
+    va_end(args);
 }

+ 81 - 81
qubes-rpc/qfile-agent.c

@@ -13,107 +13,107 @@
 #include <libqubes-rpc-filecopy.h>
 
 enum {
-	PROGRESS_FLAG_NORMAL,
-	PROGRESS_FLAG_INIT,
-	PROGRESS_FLAG_DONE
+    PROGRESS_FLAG_NORMAL,
+    PROGRESS_FLAG_INIT,
+    PROGRESS_FLAG_DONE
 };
 
 void do_notify_progress(long long total, int flag)
 {
-	const char *du_size_env = getenv("FILECOPY_TOTAL_SIZE");
-	const char *progress_type_env = getenv("PROGRESS_TYPE");
-	const char *saved_stdout_env = getenv("SAVED_FD_1");
-	int ignore;
-	if (!progress_type_env)
-		return;
-	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));
-		ignore = write(2, msg, strlen(msg));
-		if (flag == PROGRESS_FLAG_DONE)
-			ignore = write(2, "\n", 1);
-	}
-	if (!strcmp(progress_type_env, "gui") && saved_stdout_env) {
-		char msg[256];
-		snprintf(msg, sizeof(msg), "%lld\n", total);
-		ignore = write(strtoul(saved_stdout_env, NULL, 0), msg,
-				strlen(msg));
-	}
-	if (ignore < 0) {
-		/* silence gcc warning */
-	}
+    const char *du_size_env = getenv("FILECOPY_TOTAL_SIZE");
+    const char *progress_type_env = getenv("PROGRESS_TYPE");
+    const char *saved_stdout_env = getenv("SAVED_FD_1");
+    int ignore;
+    if (!progress_type_env)
+        return;
+    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));
+        ignore = write(2, msg, strlen(msg));
+        if (flag == PROGRESS_FLAG_DONE)
+            ignore = write(2, "\n", 1);
+    }
+    if (!strcmp(progress_type_env, "gui") && saved_stdout_env) {
+        char msg[256];
+        snprintf(msg, sizeof(msg), "%lld\n", total);
+        ignore = write(strtoul(saved_stdout_env, NULL, 0), msg,
+                strlen(msg));
+    }
+    if (ignore < 0) {
+        /* silence gcc warning */
+    }
 }
 
 void notify_progress(int size, int flag)
 {
-	static long long total = 0;
-	static long long prev_total = 0;
-	total += size;
-	if (total > prev_total + PROGRESS_NOTIFY_DELTA
-	    || (flag != PROGRESS_FLAG_NORMAL)) {
-		// check for possible error from qfile-unpacker; if error occured,
-		// exit() will be called, so don't bother with current state
-		// (notify_progress can be called as callback from copy_file())
-		if (flag == PROGRESS_FLAG_NORMAL)
-			wait_for_result();
-		do_notify_progress(total, flag);
-		prev_total = total;
-	}
+    static long long total = 0;
+    static long long prev_total = 0;
+    total += size;
+    if (total > prev_total + PROGRESS_NOTIFY_DELTA
+        || (flag != PROGRESS_FLAG_NORMAL)) {
+        // check for possible error from qfile-unpacker; if error occured,
+        // exit() will be called, so don't bother with current state
+        // (notify_progress can be called as callback from copy_file())
+        if (flag == PROGRESS_FLAG_NORMAL)
+            wait_for_result();
+        do_notify_progress(total, flag);
+        prev_total = total;
+    }
 }
 
 
 char *get_abs_path(const char *cwd, const char *pathname)
 {
-	char *ret;
-	if (pathname[0] == '/')
-		return strdup(pathname);
-	if (asprintf(&ret, "%s/%s", cwd, pathname) < 0)
-		return NULL;
-	else
-		return ret;
+    char *ret;
+    if (pathname[0] == '/')
+        return strdup(pathname);
+    if (asprintf(&ret, "%s/%s", cwd, pathname) < 0)
+        return NULL;
+    else
+        return ret;
 }
 
 int main(int argc, char **argv)
 {
-	int i;
-	char *entry;
-	char *cwd;
-	char *sep;
-	int ignore_symlinks = 0;
+    int i;
+    char *entry;
+    char *cwd;
+    char *sep;
+    int ignore_symlinks = 0;
 
-	qfile_pack_init();
-	register_error_handler(qfile_gui_fatal);
-	register_notify_progress(&notify_progress);
-	notify_progress(0, PROGRESS_FLAG_INIT);
-	cwd = getcwd(NULL, 0);
-	for (i = 1; i < argc; i++) {
-		if (strcmp(argv[i], "--ignore-symlinks")==0) {
-			ignore_symlinks = 1;
-			continue;
-		}
+    qfile_pack_init();
+    register_error_handler(qfile_gui_fatal);
+    register_notify_progress(&notify_progress);
+    notify_progress(0, PROGRESS_FLAG_INIT);
+    cwd = getcwd(NULL, 0);
+    for (i = 1; i < argc; i++) {
+        if (strcmp(argv[i], "--ignore-symlinks")==0) {
+            ignore_symlinks = 1;
+            continue;
+        }
 
-		entry = get_abs_path(cwd, argv[i]);
+        entry = get_abs_path(cwd, argv[i]);
 
-		do {
-			sep = rindex(entry, '/');
-			if (!sep)
-				gui_fatal
-				    ("Internal error: nonabsolute filenames not allowed");
-			*sep = 0;
-		} while (sep[1] == 0);
-		if (entry[0] == 0) {
-			if (chdir("/") < 0) {
-				gui_fatal("Internal error: chdir(\"/\") failed?!");
-			}
-		} else if (chdir(entry))
-			gui_fatal("chdir to %s", entry);
-		do_fs_walk(sep + 1, ignore_symlinks);
-		free(entry);
-	}
-	notify_end_and_wait_for_result();
-	notify_progress(0, PROGRESS_FLAG_DONE);
-	return 0;
+        do {
+            sep = rindex(entry, '/');
+            if (!sep)
+                gui_fatal
+                    ("Internal error: nonabsolute filenames not allowed");
+            *sep = 0;
+        } while (sep[1] == 0);
+        if (entry[0] == 0) {
+            if (chdir("/") < 0) {
+                gui_fatal("Internal error: chdir(\"/\") failed?!");
+            }
+        } else if (chdir(entry))
+            gui_fatal("chdir to %s", entry);
+        do_fs_walk(sep + 1, ignore_symlinks);
+        free(entry);
+    }
+    notify_end_and_wait_for_result();
+    notify_progress(0, PROGRESS_FLAG_DONE);
+    return 0;
 }
 
 

+ 68 - 68
qubes-rpc/qfile-unpacker.c

@@ -17,81 +17,81 @@
 #define INCOMING_DIR_ROOT "/home/user/QubesIncoming"
 int prepare_creds_return_uid(const char *username)
 {
-	const struct passwd *pwd;
-	pwd = getpwnam(username);
-	if (!pwd) {
-		perror("getpwnam");
-		exit(1);
-	}
-	setenv("HOME", pwd->pw_dir, 1);
-	setenv("USER", username, 1);
-	if (setgid(pwd->pw_gid) < 0)
-		gui_fatal("Error setting group permissions");
-	if (initgroups(username, pwd->pw_gid) < 0)
-		gui_fatal("Error initializing groups");
-	if (setfsuid(pwd->pw_uid) < 0)
-		gui_fatal("Error setting filesystem level permissions");
-	return pwd->pw_uid;
+    const struct passwd *pwd;
+    pwd = getpwnam(username);
+    if (!pwd) {
+        perror("getpwnam");
+        exit(1);
+    }
+    setenv("HOME", pwd->pw_dir, 1);
+    setenv("USER", username, 1);
+    if (setgid(pwd->pw_gid) < 0)
+        gui_fatal("Error setting group permissions");
+    if (initgroups(username, pwd->pw_gid) < 0)
+        gui_fatal("Error initializing groups");
+    if (setfsuid(pwd->pw_uid) < 0)
+        gui_fatal("Error setting filesystem level permissions");
+    return pwd->pw_uid;
 }
 
 int main(int argc __attribute((__unused__)), char ** argv __attribute__((__unused__)))
 {
-	char *incoming_dir;
-	int uid, ret;
-	pid_t pid;
-	const char *remote_domain;
-	char *procdir_path;
-	int procfs_fd;
+    char *incoming_dir;
+    int uid, ret;
+    pid_t pid;
+    const char *remote_domain;
+    char *procdir_path;
+    int procfs_fd;
 
-	uid = prepare_creds_return_uid("user");
+    uid = prepare_creds_return_uid("user");
 
-	remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
-	if (!remote_domain) {
-		gui_fatal("Cannot get remote domain name");
-		exit(1);
-	}
-	mkdir(INCOMING_DIR_ROOT, 0700);
-	if (asprintf(&incoming_dir, "%s/%s", INCOMING_DIR_ROOT, remote_domain) < 0)
-		gui_fatal("Error allocating memory");
-	mkdir(incoming_dir, 0700);
-	if (chdir(incoming_dir))
-		gui_fatal("Error chdir to %s", incoming_dir);
+    remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
+    if (!remote_domain) {
+        gui_fatal("Cannot get remote domain name");
+        exit(1);
+    }
+    mkdir(INCOMING_DIR_ROOT, 0700);
+    if (asprintf(&incoming_dir, "%s/%s", INCOMING_DIR_ROOT, remote_domain) < 0)
+        gui_fatal("Error allocating memory");
+    mkdir(incoming_dir, 0700);
+    if (chdir(incoming_dir))
+        gui_fatal("Error chdir to %s", incoming_dir);
 
-	if (mount(".", ".", NULL, MS_BIND | MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL) < 0)
-		gui_fatal("Failed to mount a directory %s", incoming_dir);
+    if (mount(".", ".", NULL, MS_BIND | MS_NODEV | MS_NOEXEC | MS_NOSUID, NULL) < 0)
+        gui_fatal("Failed to mount a directory %s", incoming_dir);
 
-	/* parse the input in unprivileged child process, parent will hold root
-	 * access to unmount incoming dir */
-	switch (pid=fork()) {
-		case -1:
-			gui_fatal("Failed to create new process");
-		case 0:
-			if (asprintf(&procdir_path, "/proc/%d/fd", getpid()) < 0) {
-				gui_fatal("Error allocating memory");
-			}
-			procfs_fd = open(procdir_path, O_DIRECTORY | O_RDONLY);
-			if (procfs_fd < 0)
-				perror("Failed to open /proc");
-			else
-				set_procfs_fd(procfs_fd);
-			free(procdir_path);
+    /* parse the input in unprivileged child process, parent will hold root
+     * access to unmount incoming dir */
+    switch (pid=fork()) {
+        case -1:
+            gui_fatal("Failed to create new process");
+        case 0:
+            if (asprintf(&procdir_path, "/proc/%d/fd", getpid()) < 0) {
+                gui_fatal("Error allocating memory");
+            }
+            procfs_fd = open(procdir_path, O_DIRECTORY | O_RDONLY);
+            if (procfs_fd < 0)
+                perror("Failed to open /proc");
+            else
+                set_procfs_fd(procfs_fd);
+            free(procdir_path);
 
-			if (chroot("."))
-				gui_fatal("Error chroot to %s", incoming_dir);
-			if (setuid(uid) < 0) {
-				/* no kdialog inside chroot */
-				perror("setuid");
-				exit(1);
-			}
-			return do_unpack();
-	}
-	if (waitpid(pid, &ret, 0) < 0) {
-		gui_fatal("Failed to wait for child process");
-	}
-	if (umount2(".", MNT_DETACH) < 0)
-		gui_fatal("Cannot umount incoming directory");
-	if (!WIFEXITED(ret)) {
-		gui_fatal("Child process exited abnormally");
-	}
-	return WEXITSTATUS(ret);
+            if (chroot("."))
+                gui_fatal("Error chroot to %s", incoming_dir);
+            if (setuid(uid) < 0) {
+                /* no kdialog inside chroot */
+                perror("setuid");
+                exit(1);
+            }
+            return do_unpack();
+    }
+    if (waitpid(pid, &ret, 0) < 0) {
+        gui_fatal("Failed to wait for child process");
+    }
+    if (umount2(".", MNT_DETACH) < 0)
+        gui_fatal("Cannot umount incoming directory");
+    if (!WIFEXITED(ret)) {
+        gui_fatal("Child process exited abnormally");
+    }
+    return WEXITSTATUS(ret);
 }

+ 97 - 68
qubes-rpc/qopen-in-vm.c

@@ -9,99 +9,128 @@
 #include <stdlib.h>
 #include <libqubes-rpc-filecopy.h>
 #include <unistd.h>
+#include <getopt.h>
 #include <gui-fatal.h>
 #include "dvm2.h"
 
-void send_file(const char *fname)
+void send_file(const char *fname, int view_only)
 {
-	const char *base;
-	char sendbuf[DVM_FILENAME_SIZE];
-	int fd = open(fname, O_RDONLY);
-	if (fd < 0)
-		gui_fatal("open %s", fname);
-	base = rindex(fname, '/');
-	if (!base)
-		base = fname;
-	else
-		base++;
-	if (strlen(base) >= DVM_FILENAME_SIZE)
-		base += strlen(base) - DVM_FILENAME_SIZE + 1;
-        strncpy(sendbuf,base,DVM_FILENAME_SIZE); /* fills out with NULs */
-	if (!write_all(1, sendbuf, DVM_FILENAME_SIZE))
-		gui_fatal("send filename to dispVM");
-	if (!copy_fd_all(1, fd))
-		gui_fatal("send file to dispVM");
-	close(1);
-	close(fd);
+    const char *base;
+    char sendbuf[DVM_FILENAME_SIZE] = {0};
+    size_t sendbuf_size = DVM_FILENAME_SIZE;
+    int fd = open(fname, O_RDONLY);
+    if (fd < 0)
+        gui_fatal("open %s", fname);
+
+    _Static_assert(DVM_FILENAME_SIZE > sizeof(DVM_VIEW_ONLY_PREFIX),
+            "DVM_FILENAME_SIZE > sizeof(DVM_VIEW_ONLY_PREFIX)");
+
+    if (view_only) {
+        strncpy(sendbuf, DVM_VIEW_ONLY_PREFIX, sendbuf_size);
+        sendbuf_size -= strlen(DVM_VIEW_ONLY_PREFIX);
+    }
+    base = rindex(fname, '/');
+    if (!base)
+        base = fname;
+    else
+        base++;
+    if (strlen(base) >= sendbuf_size)
+        base += strlen(base) - sendbuf_size + 1;
+    strncat(sendbuf,base,sendbuf_size - 1); /* fills out with NULs */
+    sendbuf[DVM_FILENAME_SIZE - 1] = '\0';
+    if (!write_all(1, sendbuf, DVM_FILENAME_SIZE))
+        gui_fatal("send filename to dispVM");
+    if (!copy_fd_all(1, fd))
+        gui_fatal("send file to dispVM");
+    close(1);
+    close(fd);
 }
 
 int copy_and_return_nonemptiness(int tmpfd)
 {
-	struct stat st;
-	if (!copy_fd_all(tmpfd, 0))
-		gui_fatal("receiving file from dispVM");
-	if (fstat(tmpfd, &st))
-		gui_fatal("fstat");
-	close(tmpfd);
+    struct stat st;
+    if (!copy_fd_all(tmpfd, 0))
+        gui_fatal("receiving file from dispVM");
+    if (fstat(tmpfd, &st))
+        gui_fatal("fstat");
+    close(tmpfd);
 
-	return st.st_size > 0;
+    return st.st_size > 0;
 }
 
 void recv_file_nowrite(const char *fname)
 {
-	char *tempfile;
-	char *errmsg;
-	int tmpfd = -1;
+    char *tempfile;
+    char *errmsg;
+    int tmpfd = -1;
 
-	if (asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX") != -1)
-		tmpfd = mkstemp(tempfile);
-	if (tmpfd < 0)
-		gui_fatal("unable to create any temporary file, aborting");
-	if (!copy_and_return_nonemptiness(tmpfd)) {
-		unlink(tempfile);
-		return;
-	}
-	if (asprintf(&errmsg,
-		 "The file %s has been edited in Disposable VM and the modified content has been received, "
-		 "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been "
-		 "saved to %s", fname, tempfile) != -1)
+    if (asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX") != -1)
+        tmpfd = mkstemp(tempfile);
+    if (tmpfd < 0)
+        gui_fatal("unable to create any temporary file, aborting");
+    if (!copy_and_return_nonemptiness(tmpfd)) {
+        unlink(tempfile);
+        return;
+    }
+    if (asprintf(&errmsg,
+         "The file %s has been edited in Disposable VM and the modified content has been received, "
+         "but this file is in nonwritable directory and thus cannot be modified safely. The edited file has been "
+         "saved to %s", fname, tempfile) != -1)
         gui_nonfatal(errmsg);
 }
 
 void actually_recv_file(const char *fname, const char *tempfile, int tmpfd)
 {
-	if (!copy_and_return_nonemptiness(tmpfd)) {
-		unlink(tempfile);
-		return;
-	}
-	if (rename(tempfile, fname))
-		gui_fatal("rename");
+    if (!copy_and_return_nonemptiness(tmpfd)) {
+        unlink(tempfile);
+        return;
+    }
+    if (rename(tempfile, fname))
+        gui_fatal("rename");
 }
 
 void recv_file(const char *fname)
 {
-	int tmpfd = -1;
-	char *tempfile;
-	if (asprintf(&tempfile, "%s.XXXXXX", fname) != -1) {
-		tmpfd = mkstemp(tempfile);
-	}
-	if (tmpfd < 0)
-		recv_file_nowrite(fname);
-	else
-		actually_recv_file(fname, tempfile, tmpfd);
-}
-
-void talk_to_daemon(const char *fname)
-{
-	send_file(fname);
-	recv_file(fname);
+    int tmpfd = -1;
+    char *tempfile;
+    if (asprintf(&tempfile, "%s.XXXXXX", fname) != -1) {
+        tmpfd = mkstemp(tempfile);
+    }
+    if (tmpfd < 0)
+        recv_file_nowrite(fname);
+    else
+        actually_recv_file(fname, tempfile, tmpfd);
 }
 
 int main(int argc, char ** argv)
 {
-	signal(SIGPIPE, SIG_IGN);
-	if (argc!=2)
-		gui_fatal("OpenInVM - no file given?");
-	talk_to_daemon(argv[1]);
-	return 0;
+    char *fname;
+    int view_only = 0;
+    int ret;
+    const struct option opts[] = {
+        {"view-only", no_argument, &view_only, 1},
+        {0}
+    };
+
+    while ((ret=getopt_long(argc, argv, "", opts, NULL)) != -1) {
+        if (ret == '?') {
+            exit(2);
+        }
+    }
+
+    signal(SIGPIPE, SIG_IGN);
+
+    if (optind >= argc)
+        gui_fatal("OpenInVM - no file given?");
+    fname = argv[optind];
+    send_file(fname, view_only);
+    if (!view_only) {
+        recv_file(fname);
+    } else {
+        /* discard received data */
+        int null_fd = open("/dev/null", O_WRONLY);
+        copy_fd_all(null_fd, 0);
+        close(null_fd);
+    }
+    return 0;
 }

+ 6 - 0
qubes-rpc/qvm-actions.sh

@@ -45,6 +45,12 @@ case "$action" in
             qvm-open-in-dvm "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 &
         done
         ;;
+    viewdvm)
+        for file in "$@"
+        do
+            qvm-open-in-dvm --view-only "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 &
+        done
+        ;;
     *)
         echo "Unknown action. Aborting..."
         exit 1

+ 7 - 2
qubes-rpc/qvm-dvm.desktop

@@ -1,10 +1,15 @@
 [Desktop Entry]
-Actions=QvmDvm;
+Actions=QvmDvm;QvmViewDvm
 Type=Service
 X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles
 
 [Desktop Action QvmDvm]
 Exec=/usr/bin/qvm-open-in-dvm %U
 Icon=kget
-Name=Open In DisposableVM
+Name=Edit In DisposableVM
+
+[Desktop Action QvmViewDvm]
+Exec=/usr/bin/qvm-open-in-dvm --view-only %U
+Icon=kget
+Name=View In DisposableVM
 

+ 3 - 3
qubes-rpc/qvm-open-in-dvm

@@ -20,10 +20,10 @@
 #
 #
 
-if ! [ $# = 1 ] ; then
-	echo "Usage: $0 filename"
+if ! [ $# = 1 ] && ! [ $# = 2 ]; then
+	echo "Usage: $0 [--view-only] filename"
 	exit 1
 fi
 
 # shellcheck disable=SC2016
-exec qvm-open-in-vm '$dispvm' "$1"
+exec qvm-open-in-vm '$dispvm' "$@"

+ 27 - 6
qubes-rpc/qvm-open-in-vm

@@ -20,16 +20,37 @@
 #
 #
 
-if ! [ $# = 2 ] ; then
-	echo "Usage: $0 vmname filename"
-	exit 1
+usage() {
+	echo "Usage: $0 [--view-only] vmname filename"
+	exit 2
+}
+
+qopen_opts=
+target=
+filename=
+
+while [ $# -gt 0 ]; do
+    if [ "x$1" = "x--view-only" ]; then
+        qopen_opts=--view-only
+    elif [ -z "$target" ]; then
+        target="$1"
+    elif [ -z "$filename" ]; then
+        filename="$1"
+    else
+        usage
+    fi
+    shift
+done
+
+if [ -z "$target" ] || [ -z "$filename" ]; then
+    usage
 fi
 
-case "$2" in
+case "$filename" in
 	*://*)
-        exec /usr/lib/qubes/qrexec-client-vm "$1" qubes.OpenURL /bin/echo "$2"
+        exec /usr/lib/qubes/qrexec-client-vm "$target" qubes.OpenURL /bin/echo "$filename"
         ;;
     *)
-        exec /usr/lib/qubes/qrexec-client-vm "$1" qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" "$2"
+        exec /usr/lib/qubes/qrexec-client-vm "$target" qubes.OpenInVM "/usr/lib/qubes/qopen-in-vm" $qopen_opts "$filename"
         ;;
 esac

+ 6 - 8
qubes-rpc/qvm_copy_nautilus.py

@@ -26,11 +26,9 @@ class CopyToAppvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
     def on_menu_item_clicked(self, menu, files):
         '''Called when user chooses files though Nautilus context menu.
         '''
-        for file_obj in files:
-
-            # Check if file still exists
-            if file_obj.is_gone():
-                return
-
-            gio_file = file_obj.get_location()
-            subprocess.call(['/usr/lib/qubes/qvm-copy-to-vm.gnome', gio_file.get_path()])
+        cmd = [file_obj.get_location().get_path()
+               for file_obj in files
+               # Check if file is not gone
+               if not file_obj.is_gone()]
+        cmd.insert(0, '/usr/lib/qubes/qvm-copy-to-vm.gnome')
+        subprocess.call(cmd)

+ 20 - 6
qubes-rpc/qvm_dvm_nautilus.py

@@ -17,15 +17,24 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
         if not files:
             return
 
-        menu_item = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm',
-                                      label='Open In DisposableVM',
+        menu_item1 = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm',
+                                      label='Edit In DisposableVM',
                                       tip='',
                                       icon='')
 
-        menu_item.connect('activate', self.on_menu_item_clicked, files)
-        return menu_item,
+        menu_item1.connect('activate', self.on_menu_item_clicked, files)
 
-    def on_menu_item_clicked(self, menu, files):
+        menu_item2 = Nautilus.MenuItem(name='QubesMenuProvider::ViewInDvm',
+                                      label='View In DisposableVM',
+                                      tip='',
+                                      icon='')
+
+        menu_item2.connect('activate',
+                self.on_menu_item_clicked,
+                files, True)
+        return menu_item1, menu_item2,
+
+    def on_menu_item_clicked(self, menu, files, view_only=False):
         '''Called when user chooses files though Nautilus context menu.
         '''
         for file_obj in files:
@@ -38,6 +47,11 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
 
             # Use subprocess.DEVNULL in python >= 3.3
             devnull = open(os.devnull, 'wb')
+            command = ['nohup', '/usr/bin/qvm-open-in-dvm']
+            if view_only:
+                command.append('--view-only')
+            command.append(gio_file.get_path())
 
             # Use Popen instead of subprocess.call to spawn the process
-            Popen(['nohup', '/usr/bin/qvm-open-in-dvm', gio_file.get_path()], stdout=devnull, stderr=devnull)
+            Popen(command, stdout=devnull, stderr=devnull)
+            devnull.close()

+ 6 - 8
qubes-rpc/qvm_move_nautilus.py

@@ -26,11 +26,9 @@ class MoveToAppvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
     def on_menu_item_clicked(self, menu, files):
         '''Called when user chooses files though Nautilus context menu.
         '''
-        for file_obj in files:
-
-            # Check if file still exists
-            if file_obj.is_gone():
-                return
-
-            gio_file = file_obj.get_location()
-            subprocess.call(['/usr/lib/qubes/qvm-move-to-vm.gnome', gio_file.get_path()])
+        cmd = [file_obj.get_location().get_path()
+               for file_obj in files
+               # Check if file is not gone
+               if not file_obj.is_gone()]
+        cmd.insert(0, '/usr/lib/qubes/qvm-move-to-vm.gnome')
+        subprocess.call(cmd)

+ 10 - 7
qubes-rpc/tar2qfile.c

@@ -708,6 +708,7 @@ ustar_rd (int fd, struct file_header * untrusted_hdr, char *buf, struct stat * s
         // Split the path in directories and recompose it incrementally
 	char * last_token = strtok(dirbuf,"/");
 	char * token = strtok(NULL, "/");
+	size_t len_last_token = 0;
 	while (token != NULL) {
 
 #ifdef DEBUG
@@ -715,21 +716,22 @@ ustar_rd (int fd, struct file_header * untrusted_hdr, char *buf, struct stat * s
 #endif
 
 		// Recompose the path based on last discovered directory
+		len_last_token = strlen(last_token);
 		if (path == NULL) {
-			path = malloc(sizeof (char) * (strlen(last_token)+1));
+			path = malloc(sizeof (char) * (len_last_token+1));
 			if (path == NULL)
 				return MEMORY_ALLOC_FAILED;
-			path = strncpy(path, last_token, strlen(last_token));
-			path[strlen(last_token)] = '\0';
+			path = memcpy(path, last_token, len_last_token);
+			path[len_last_token] = '\0';
 		} else {
 			pathsize = strlen(path);
-			path = realloc(path, sizeof (char) * (strlen(path)+1+strlen(last_token)+1));
+			path = realloc(path, sizeof (char) * (strlen(path)+1+len_last_token+1));
 			if (path == NULL)
 				return MEMORY_ALLOC_FAILED;
 			path[pathsize] = '/';
 
-			strncpy(path+pathsize+1, last_token, strlen(last_token));
-			path[pathsize+strlen(last_token)+1] = '\0';
+			memcpy(path+pathsize+1, last_token, len_last_token);
+			path[pathsize+len_last_token+1] = '\0';
 		}
 #ifdef DEBUG
 		fprintf(stderr,"Path is %s\n",path);
@@ -762,7 +764,8 @@ ustar_rd (int fd, struct file_header * untrusted_hdr, char *buf, struct stat * s
 			dirs_headers_sent[n_dirs-1] = malloc(sizeof (char) * (strlen(path)+1));
 			if (dirs_headers_sent[n_dirs-1] == NULL)
 				return MEMORY_ALLOC_FAILED;
-			strncpy(dirs_headers_sent[n_dirs-1], path, strlen(path)+1);
+
+			memcpy(dirs_headers_sent[n_dirs-1], path, strlen(path)+1);
 
                         // Initialize the qfile headers for the current directory path
 			dir_header.namelen = strlen(path)+1;

+ 189 - 179
qubes-rpc/vm-file-editor.c

@@ -19,217 +19,227 @@ static const char *cleanup_dirname = NULL;
 
 static void cleanup_file(void)
 {
-	if (cleanup_filename) {
-		if (unlink(cleanup_filename) < 0)
-			fprintf(stderr, "Failed to remove file at exit\n");
-		cleanup_filename = NULL;
-	}
-	if (cleanup_dirname) {
-		if (rmdir(cleanup_dirname) < 0)
-			fprintf(stderr, "Failed to remove directory at exit\n");
-		cleanup_dirname = NULL;
-	}
+    if (cleanup_filename) {
+        if (unlink(cleanup_filename) < 0)
+            fprintf(stderr, "Failed to remove file at exit\n");
+        cleanup_filename = NULL;
+    }
+    if (cleanup_dirname) {
+        if (rmdir(cleanup_dirname) < 0)
+            fprintf(stderr, "Failed to remove directory at exit\n");
+        cleanup_dirname = NULL;
+    }
 }
 
 const char *gettime(void)
 {
-	static char retbuf[60];
-	struct timeval tv;
-	gettimeofday(&tv, NULL);
-	snprintf(retbuf, sizeof(retbuf), "%lld.%06lld",
-		 (long long) tv.tv_sec, (long long) tv.tv_usec);
-	return retbuf;
+    static char retbuf[60];
+    struct timeval tv;
+    gettimeofday(&tv, NULL);
+    snprintf(retbuf, sizeof(retbuf), "%lld.%06lld",
+         (long long) tv.tv_sec, (long long) tv.tv_usec);
+    return retbuf;
 }
 
 static char *get_directory(void)
 {
-	const char *remote_domain;
-	char *dir;
-	size_t len;
-	char *ret;
-
-	remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
-	if (!remote_domain) {
-		fprintf(stderr, "Cannot get remote domain name\n");
-		exit(1);
-	}
-	if (!*remote_domain || index(remote_domain, '/'))
-		goto fail;
-	if (!strcmp(remote_domain, ".") || !strcmp(remote_domain, ".."))
-		goto fail;
-
-	len = strlen("/tmp/-XXXXXX")+strlen(remote_domain)+1;
-	dir = malloc(len);
-	if (!dir) {
-		fprintf(stderr, "Cannot allocate memory\n");
-		exit(1);
-	}
-	snprintf(dir, len, "/tmp/%s-XXXXXX", remote_domain);
-
-	ret = mkdtemp(dir);
-	if (ret == NULL) {
-		perror("mkdtemp");
-		exit(1);
-	}
-	cleanup_dirname = strdup(ret);
-	return ret;
+    const char *remote_domain;
+    char *dir;
+    size_t len;
+    char *ret;
+
+    remote_domain = getenv("QREXEC_REMOTE_DOMAIN");
+    if (!remote_domain) {
+        fprintf(stderr, "Cannot get remote domain name\n");
+        exit(1);
+    }
+    if (!*remote_domain || index(remote_domain, '/'))
+        goto fail;
+    if (!strcmp(remote_domain, ".") || !strcmp(remote_domain, ".."))
+        goto fail;
+
+    len = strlen("/tmp/-XXXXXX")+strlen(remote_domain)+1;
+    dir = malloc(len);
+    if (!dir) {
+        fprintf(stderr, "Cannot allocate memory\n");
+        exit(1);
+    }
+    snprintf(dir, len, "/tmp/%s-XXXXXX", remote_domain);
+
+    ret = mkdtemp(dir);
+    if (ret == NULL) {
+        perror("mkdtemp");
+        exit(1);
+    }
+    cleanup_dirname = strdup(ret);
+    return ret;
 
 fail:
-	fprintf(stderr, "Invalid remote domain name: %s\n", remote_domain);
-	exit(1);
+    fprintf(stderr, "Invalid remote domain name: %s\n", remote_domain);
+    exit(1);
 }
 
-char *get_filename(void)
+char *get_filename(int *view_only)
 {
-	char buf[DVM_FILENAME_SIZE];
-	static char *retname;
-	int i;
-	char *directory;
-	size_t len;
-
-	directory = get_directory();
-	if (!read_all(0, buf, sizeof(buf)))
-		exit(1);
-	buf[DVM_FILENAME_SIZE-1] = 0;
-	if (index(buf, '/')) {
-		fprintf(stderr, "filename contains /");
-		exit(1);
-	}
-	for (i=0; buf[i]!=0; i++) {
-		// replace some characters with _ (eg mimeopen have problems with some of them)
-		if (index(" !?\"#$%^&*()[]<>;`~|", buf[i]))
-			buf[i]='_';
-	}
-	len = strlen(directory)+1+strlen(buf)+1;
-	retname = malloc(len);
-	if (!retname) {
-		fprintf(stderr, "Cannot allocate memory\n");
-		exit(1);
-	}
-	snprintf(retname, len, "%s/%s", directory, buf);
-	free(directory);
-	return retname;
+    char buf[DVM_FILENAME_SIZE];
+    char *fname = buf;
+    static char *retname;
+    int i;
+    char *directory;
+    size_t len;
+
+    directory = get_directory();
+    if (!read_all(0, buf, sizeof(buf)))
+        exit(1);
+    buf[DVM_FILENAME_SIZE-1] = 0;
+    if (index(buf, '/')) {
+        fprintf(stderr, "filename contains /");
+        exit(1);
+    }
+    for (i=0; buf[i]!=0; i++) {
+        // replace some characters with _ (eg mimeopen have problems with some of them)
+        if (index(" !?\"#$%^&*()[]<>;`~|", buf[i]))
+            buf[i]='_';
+    }
+    if (strncmp(buf, DVM_VIEW_ONLY_PREFIX, strlen(DVM_VIEW_ONLY_PREFIX)) == 0) {
+        *view_only = 1;
+        fname += strlen(DVM_VIEW_ONLY_PREFIX);
+    }
+    len = strlen(directory)+1+strlen(fname)+1;
+    retname = malloc(len);
+    if (!retname) {
+        fprintf(stderr, "Cannot allocate memory\n");
+        exit(1);
+    }
+    snprintf(retname, len, "%s/%s", directory, fname);
+    free(directory);
+    return retname;
 }
 
 void copy_file_by_name(const char *filename)
 {
-	int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
-	if (fd < 0) {
-		perror("open file");
-		exit(1);
-	}
-	/* we now have created a new file, ensure we delete it at the end */
-	cleanup_filename = strdup(filename);
-	atexit(cleanup_file);
-	if (!copy_fd_all(fd, 0))
+    int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
+    if (fd < 0) {
+        perror("open file");
+        exit(1);
+    }
+    /* we now have created a new file, ensure we delete it at the end */
+    cleanup_filename = strdup(filename);
+    atexit(cleanup_file);
+    if (!copy_fd_all(fd, 0))
         exit(1);
-	close(fd);
+    close(fd);
 }
 
 void send_file_back(const char * filename)
 {
-	int fd = open(filename, O_RDONLY);
-	if (fd < 0) {
-		perror("open file");
-		exit(1);
-	}
-	if (!copy_fd_all(1, fd))
-	 exit(1);
-	close(fd);
-	close(1);
+    int fd = open(filename, O_RDONLY);
+    if (fd < 0) {
+        perror("open file");
+        exit(1);
+    }
+    if (!copy_fd_all(1, fd))
+     exit(1);
+    close(fd);
+    close(1);
 }
 
 int
 main()
 {
-	struct stat stat_pre, stat_post, session_stat;
-	char *filename = get_filename();
-	int child, status, log_fd, null_fd;
-	FILE *waiter_pidfile;
-
-	copy_file_by_name(filename);
-	if (stat(filename, &stat_pre)) {
-		perror("stat pre");
-		exit(1);
-	}
+    struct stat stat_pre, stat_post, session_stat;
+    int view_only = 0;
+    char *filename = get_filename(&view_only);
+    int child, status, log_fd, null_fd;
+    FILE *waiter_pidfile;
+
+    copy_file_by_name(filename);
+    if (view_only) {
+        // mark file as read-only so applications will signal it to the user
+        chmod(filename, 0400);
+    }
+    if (stat(filename, &stat_pre)) {
+        perror("stat pre");
+        exit(1);
+    }
 #ifdef DEBUG
-	fprintf(stderr, "time=%s, waiting for qubes-session\n", gettime());
+    fprintf(stderr, "time=%s, waiting for qubes-session\n", gettime());
 #endif
-	// wait for X server to starts (especially in DispVM)
-	if (stat("/tmp/qubes-session-env", &session_stat)) {
-		switch (child = fork()) {
-			case -1:
-				perror("fork");
-				exit(1);
-			case 0:
-				waiter_pidfile = fopen("/tmp/qubes-session-waiter", "a");
-				if (waiter_pidfile == NULL) {
-					perror("fopen waiter_pidfile");
-					exit(1);
-				}
-				fprintf(waiter_pidfile, "%d\n", getpid());
-				fclose(waiter_pidfile);
-				// check the second time, to prevent race
-				if (stat("/tmp/qubes-session-env", &session_stat)) {
-					// wait for qubes-session notify
-					pause();
-				}
-				exit(0);
-			default:
-				waitpid(child, &status, 0);
-				if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
-					//propagate exit code from child
-					exit(WEXITSTATUS(status));
-				}
-		}
-	}
+    // wait for X server to starts (especially in DispVM)
+    if (stat("/tmp/qubes-session-env", &session_stat)) {
+        switch (child = fork()) {
+            case -1:
+                perror("fork");
+                exit(1);
+            case 0:
+                waiter_pidfile = fopen("/tmp/qubes-session-waiter", "a");
+                if (waiter_pidfile == NULL) {
+                    perror("fopen waiter_pidfile");
+                    exit(1);
+                }
+                fprintf(waiter_pidfile, "%d\n", getpid());
+                fclose(waiter_pidfile);
+                // check the second time, to prevent race
+                if (stat("/tmp/qubes-session-env", &session_stat)) {
+                    // wait for qubes-session notify
+                    pause();
+                }
+                exit(0);
+            default:
+                waitpid(child, &status, 0);
+                if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+                    //propagate exit code from child
+                    exit(WEXITSTATUS(status));
+                }
+        }
+    }
 #ifdef DEBUG
-	fprintf(stderr, "time=%s, starting editor\n", gettime());
+    fprintf(stderr, "time=%s, starting editor\n", gettime());
 #endif
-	switch (child = fork()) {
-		case -1:
-			perror("fork");
-			exit(1);
-		case 0:
-			null_fd = open("/dev/null", O_RDONLY);
-			dup2(null_fd, 0);
-			close(null_fd);
-
-			log_fd = open("/tmp/mimeopen.log", O_CREAT | O_APPEND, 0666);
-			if (log_fd == -1) {
-				perror("open /tmp/mimeopen.log");
-				exit(1);
-			}
-			dup2(log_fd, 1);
-			close(log_fd);
-
-			setenv("HOME", USER_HOME, 1);
-			setenv("DISPLAY", ":0", 1);
-			execl("/usr/bin/qubes-open", "qubes-open", filename, (char*)NULL);
-			perror("execl");
-			exit(1);
-		default:
-			waitpid(child, &status, 0);
-			if (status != 0) {
-				char cmd[512];
+    switch (child = fork()) {
+        case -1:
+            perror("fork");
+            exit(1);
+        case 0:
+            null_fd = open("/dev/null", O_RDONLY);
+            dup2(null_fd, 0);
+            close(null_fd);
+
+            log_fd = open("/tmp/mimeopen.log", O_CREAT | O_APPEND, 0666);
+            if (log_fd == -1) {
+                perror("open /tmp/mimeopen.log");
+                exit(1);
+            }
+            dup2(log_fd, 1);
+            close(log_fd);
+
+            setenv("HOME", USER_HOME, 1);
+            setenv("DISPLAY", ":0", 1);
+            execl("/usr/bin/qubes-open", "qubes-open", filename, (char*)NULL);
+            perror("execl");
+            exit(1);
+        default:
+            waitpid(child, &status, 0);
+            if (status != 0) {
+                char cmd[512];
 #ifdef USE_KDIALOG
-				snprintf(cmd, sizeof(cmd),
-						"HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
-					("HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                snprintf(cmd, sizeof(cmd),
+                        "HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                    ("HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
 #else
-				snprintf(cmd, sizeof(cmd),
-						"HOME=/home/user DISPLAY=:0 /usr/bin/zenity --error --text 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
+                snprintf(cmd, sizeof(cmd),
+                        "HOME=/home/user DISPLAY=:0 /usr/bin/zenity --error --text 'Unable to handle mimetype of the requested file (exit status: %d)!' > /tmp/kdialog.log 2>&1 </dev/null", status);
 #endif
-				status = system(cmd);
-			}
-	}
-
-	if (stat(filename, &stat_post)) {
-		perror("stat post");
-		exit(1);
-	}
-	if (stat_pre.st_mtime != stat_post.st_mtime)
-		send_file_back(filename);
-	free(filename);
-	return 0;
+                status = system(cmd);
+            }
+    }
+
+    if (stat(filename, &stat_post)) {
+        perror("stat post");
+        exit(1);
+    }
+    if (stat_pre.st_mtime != stat_post.st_mtime)
+        send_file_back(filename);
+    free(filename);
+    return 0;
 }

+ 46 - 6
qubesagent/firewall.py

@@ -54,6 +54,22 @@ class FirewallWorker(object):
         '''Create appropriate chains/tables'''
         raise NotImplementedError
 
+    def sd_notify(self, state):
+        '''Send notification to systemd, if available'''
+        # based on sdnotify python module
+        if not 'NOTIFY_SOCKET' in os.environ:
+            return
+        addr = os.environ['NOTIFY_SOCKET']
+        if addr[0] == '@':
+            addr = '\0' + addr[1:]
+        try:
+            sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+            sock.connect(addr)
+            sock.sendall(state.encode())
+        except:
+            # generally ignore error on systemd notification
+            pass
+
     def cleanup(self):
         '''Remove tables/chains - reverse work done by init'''
         raise NotImplementedError
@@ -155,6 +171,7 @@ class FirewallWorker(object):
         self.init()
         self.run_firewall_dir()
         self.run_user_script()
+        self.sd_notify('READY=1')
         # initial load
         for source_addr in self.list_targets():
             self.handle_addr(source_addr)
@@ -313,6 +330,16 @@ class IptablesWorker(FirewallWorker):
             if dsthosts is None:
                 dsthosts = [None]
 
+            if rule['action'] == 'accept':
+                action = 'ACCEPT'
+            elif rule['action'] == 'drop':
+                action = 'REJECT --reject-with {}'.format(
+                    'icmp6-adm-prohibited' if family == 6 else
+                    'icmp-admin-prohibited')
+            else:
+                raise RuleParseError(
+                    'Invalid rule action {}'.format(rule['action']))
+
             # sorting here is only to ease writing tests
             for proto in sorted(protos):
                 for dsthost in sorted(dsthosts):
@@ -325,8 +352,7 @@ class IptablesWorker(FirewallWorker):
                         ipt_rule += ' --dport {}'.format(dstports)
                     if icmptype is not None:
                         ipt_rule += ' --icmp-type {}'.format(icmptype)
-                    ipt_rule += ' -j {}\n'.format(
-                        str(rule['action']).upper())
+                    ipt_rule += ' -j {}\n'.format(action)
                     iptables += ipt_rule
 
         iptables += 'COMMIT\n'
@@ -370,8 +396,12 @@ class IptablesWorker(FirewallWorker):
         # starting qubes-firewall
         try:
             self.run_ipt(4, ['-F', 'QBS-FORWARD'])
+            self.run_ipt(4,
+                ['-A', 'QBS-FORWARD', '!', '-i', 'vif+', '-j', 'RETURN'])
             self.run_ipt(4, ['-A', 'QBS-FORWARD', '-j', 'DROP'])
             self.run_ipt(6, ['-F', 'QBS-FORWARD'])
+            self.run_ipt(6,
+                ['-A', 'QBS-FORWARD', '!', '-i', 'vif+', '-j', 'RETURN'])
             self.run_ipt(6, ['-A', 'QBS-FORWARD', '-j', 'DROP'])
         except subprocess.CalledProcessError:
             self.log_error('\'QBS-FORWARD\' chain not found, create it first')
@@ -470,6 +500,15 @@ class NftablesWorker(FirewallWorker):
 
             nft_rule = ""
 
+            if rule['action'] == 'accept':
+                action = 'accept'
+            elif rule['action'] == 'drop':
+                action = 'reject with icmp{} type admin-prohibited'.format(
+                    'v6' if family == 6 else '')
+            else:
+                raise RuleParseError(
+                    'Invalid rule action {}'.format(rule['action']))
+
             if 'proto' in rule:
                 if family == 4:
                     nft_rule += ' ip protocol {}'.format(rule['proto'])
@@ -523,16 +562,16 @@ class NftablesWorker(FirewallWorker):
                 if 'proto' not in rule:
                     nft_rules.append(
                         nft_rule + ' tcp dport {} {}'.format(
-                            dstports, rule['action']))
+                            dstports, action))
                     nft_rules.append(
                         nft_rule + ' udp dport {} {}'.format(
-                            dstports, rule['action']))
+                            dstports, action))
                 else:
                     nft_rules.append(
                         nft_rule + ' {} dport {} {}'.format(
-                            rule['proto'], dstports, rule['action']))
+                            rule['proto'], dstports, action))
             else:
-                nft_rules.append(nft_rule + ' ' + rule['action'])
+                nft_rules.append(nft_rule + ' ' + action)
 
         return (
             'flush chain {family} {table} {chain}\n'
@@ -579,6 +618,7 @@ class NftablesWorker(FirewallWorker):
             '    type filter hook forward priority 0;\n'
             '    policy drop;\n'
             '    ct state established,related accept\n'
+            '    meta iifname != "vif*" accept\n'
             '  }}\n'
             '}}\n'
         )

+ 33 - 18
qubesagent/test_firewall.py

@@ -198,10 +198,14 @@ class TestIptablesWorker(TestCase):
             "-A chain -d 2.2.2.2/32 -p tcp --dport 53:53 -j ACCEPT\n"
             "-A chain -d 1.1.1.1/32 -p udp --dport 53:53 -j ACCEPT\n"
             "-A chain -d 2.2.2.2/32 -p udp --dport 53:53 -j ACCEPT\n"
-            "-A chain -d 1.1.1.1/32 -p udp --dport 53:53 -j DROP\n"
-            "-A chain -d 2.2.2.2/32 -p udp --dport 53:53 -j DROP\n"
-            "-A chain -p icmp -j DROP\n"
-            "-A chain -j DROP\n"
+            "-A chain -d 1.1.1.1/32 -p udp --dport 53:53 -j REJECT "
+            "--reject-with icmp-admin-prohibited\n"
+            "-A chain -d 2.2.2.2/32 -p udp --dport 53:53 -j REJECT "
+            "--reject-with icmp-admin-prohibited\n"
+            "-A chain -p icmp -j REJECT "
+            "--reject-with icmp-admin-prohibited\n"
+            "-A chain -j REJECT "
+            "--reject-with icmp-admin-prohibited\n"
             "COMMIT\n"
         )
         self.assertEqual(self.obj.prepare_rules('chain', rules, 4),
@@ -232,10 +236,14 @@ class TestIptablesWorker(TestCase):
             "-A chain -d 2001::2/128 -p tcp --dport 53:53 -j ACCEPT\n"
             "-A chain -d 2001::1/128 -p udp --dport 53:53 -j ACCEPT\n"
             "-A chain -d 2001::2/128 -p udp --dport 53:53 -j ACCEPT\n"
-            "-A chain -d 2001::1/128 -p udp --dport 53:53 -j DROP\n"
-            "-A chain -d 2001::2/128 -p udp --dport 53:53 -j DROP\n"
-            "-A chain -p icmpv6 -j DROP\n"
-            "-A chain -j DROP\n"
+            "-A chain -d 2001::1/128 -p udp --dport 53:53 -j REJECT "
+            "--reject-with icmp6-adm-prohibited\n"
+            "-A chain -d 2001::2/128 -p udp --dport 53:53 -j REJECT "
+            "--reject-with icmp6-adm-prohibited\n"
+            "-A chain -p icmpv6 -j REJECT "
+            "--reject-with icmp6-adm-prohibited\n"
+            "-A chain -j REJECT "
+            "--reject-with icmp6-adm-prohibited\n"
             "COMMIT\n"
         )
         self.assertEqual(self.obj.prepare_rules('chain', rules, 6),
@@ -271,10 +279,14 @@ class TestIptablesWorker(TestCase):
 
     def test_006_init(self):
         self.obj.init()
-        self.assertEqual(self.obj.called_commands[4],
-            [['-F', 'QBS-FORWARD'], ['-A', 'QBS-FORWARD', '-j', 'DROP']])
-        self.assertEqual(self.obj.called_commands[6],
-            [['-F', 'QBS-FORWARD'], ['-A', 'QBS-FORWARD', '-j', 'DROP']])
+        self.assertEqual(self.obj.called_commands[4], [
+            ['-F', 'QBS-FORWARD'],
+            ['-A', 'QBS-FORWARD', '!', '-i', 'vif+', '-j', 'RETURN'],
+            ['-A', 'QBS-FORWARD', '-j', 'DROP']])
+        self.assertEqual(self.obj.called_commands[6], [
+            ['-F', 'QBS-FORWARD'],
+            ['-A', 'QBS-FORWARD', '!', '-i', 'vif+', '-j', 'RETURN'],
+            ['-A', 'QBS-FORWARD', '-j', 'DROP']])
 
     def test_007_cleanup(self):
         self.obj.init()
@@ -363,9 +375,9 @@ class TestNftablesWorker(TestCase):
             '    ip daddr { 1.1.1.1/32, 2.2.2.2/32 } tcp dport 53 accept\n'
             '    ip daddr { 1.1.1.1/32, 2.2.2.2/32 } udp dport 53 accept\n'
             '    ip protocol udp ip daddr { 1.1.1.1/32, 2.2.2.2/32 } udp dport '
-            '53 drop\n'
-            '    ip protocol icmp drop\n'
-            '    drop\n'
+            '53 reject with icmp type admin-prohibited\n'
+            '    ip protocol icmp reject with icmp type admin-prohibited\n'
+            '    reject with icmp type admin-prohibited\n'
             '  }\n'
             '}\n'
         )
@@ -399,9 +411,10 @@ class TestNftablesWorker(TestCase):
             '    ip6 daddr { 2001::1/128, 2001::2/128 } tcp dport 53 accept\n'
             '    ip6 daddr { 2001::1/128, 2001::2/128 } udp dport 53 accept\n'
             '    ip6 nexthdr udp ip6 daddr { 2001::1/128, 2001::2/128 } '
-            'udp dport 53 drop\n'
-            '    ip6 nexthdr icmpv6 icmpv6 type 128 drop\n'
-            '    drop\n'
+            'udp dport 53 reject with icmpv6 type admin-prohibited\n'
+            '    ip6 nexthdr icmpv6 icmpv6 type 128 reject with icmpv6 type '
+            'admin-prohibited\n'
+            '    reject with icmpv6 type admin-prohibited\n'
             '  }\n'
             '}\n'
         )
@@ -435,6 +448,7 @@ class TestNftablesWorker(TestCase):
             '    type filter hook forward priority 0;\n'
             '    policy drop;\n'
             '    ct state established,related accept\n'
+            '    meta iifname != "vif*" accept\n'
             '  }\n'
             '}\n'
             'table ip6 qubes-firewall {\n'
@@ -442,6 +456,7 @@ class TestNftablesWorker(TestCase):
             '    type filter hook forward priority 0;\n'
             '    policy drop;\n'
             '    ct state established,related accept\n'
+            '    meta iifname != "vif*" accept\n'
             '  }\n'
             '}\n'
         ])

+ 17 - 4
misc/qubesxdg.py → qubesagent/xdg.py

@@ -1,9 +1,15 @@
 #!/usr/bin/python
 
-from gi.repository import Gio
+from gi.repository import Gio  # pylint: disable=import-error
+from gi.repository import GLib  # pylint: disable=import-error
 import sys
+import os
 
-def launch(desktop, *files):
+def pid_callback(launcher, pid, pid_list):
+    pid_list.append(pid)
+
+def launch(desktop, *files, **kwargs):
+    wait = kwargs.pop('wait', True)
     launcher = Gio.DesktopAppInfo.new_from_filename(desktop)
     try:
         import dbus
@@ -18,10 +24,17 @@ def launch(desktop, *files):
                     bus.start_service_by_name(service_id)
                 except dbus.DBusException:
                     pass
-        return launcher.launch(files, None)
     except ImportError:
         pass
-    launcher.launch(files, None)
+    if wait:
+        pid_list = []
+        flags = GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD
+        launcher.launch_uris_as_manager(files, None, flags, None, None,
+                pid_callback, pid_list)
+        for pid in pid_list:
+            os.waitpid(pid, 0)
+    else:
+        launcher.launch(files, None)
 
 if __name__ == "__main__":
     launch(*sys.argv[1:])

+ 33 - 49
rpm_spec/core-agent.spec → rpm_spec/core-agent.spec.in

@@ -23,9 +23,6 @@
 %define qubes_services qubes-core qubes-core-netvm qubes-core-early qubes-firewall qubes-iptables qubes-updates-proxy qubes-qrexec-agent qubes-updates-proxy-forwarder
 %define qubes_preset_file 75-qubes-vm.preset
 
-%{!?version: %define version %(cat version)}
-%{!?backend_vmm: %define backend_vmm %(echo $BACKEND_VMM)}
-
 %define scriptletfuns is_static() { \
     [ -f "%{_unitdir}/$1" ] && ! grep -q '^[[].nstall]' "%{_unitdir}/$1" \
 } \
@@ -104,7 +101,7 @@ restore_units() { \
 } \
 
 Name:		qubes-core-agent
-Version:	%{version}
+Version:	@VERSION@
 Release:	1%{dist}
 Summary:	The Qubes core files for VM
 
@@ -112,14 +109,9 @@ Group:		Qubes
 Vendor:		Invisible Things Lab
 License:	GPL
 URL:		http://www.qubes-os.org
-%if %{fedora} < 22
-Requires:   yum-plugin-post-transaction-actions
-%endif
-%if %{fedora} >= 18
-# Fedora >= 18 defaults to firewalld, which isn't supported nor needed by Qubes
+
 Conflicts:  firewalld
-%endif
-Requires:	xdg-utils
+Requires:   xdg-utils
 Requires:   qubes-utils >= 3.1.3
 Requires:   qubes-utils-libs >= 4.0.16
 Requires:   initscripts
@@ -137,10 +129,11 @@ Requires:   python2-qubesdb
 Requires:   ImageMagick
 Requires:   librsvg2-tools
 Requires:   zenity
+Requires:   dconf
 Requires:   qubes-core-agent-qrexec
 Requires:   qubes-libvchan
 Requires:   qubes-db-vm
-%if 0%{fedora} >= 23
+%if 0%{?fedora} >= 23
 Requires:   python3-dnf-plugins-qubes-hooks
 %else
 Requires:   python2-dnf-plugins-qubes-hooks
@@ -158,8 +151,13 @@ BuildRequires: pandoc
 BuildRequires: xen-devel
 BuildRequires: libX11-devel
 BuildRequires: qubes-utils-devel >= 3.1.3
-BuildRequires: qubes-libvchan-%{backend_vmm}-devel
+BuildRequires: qubes-libvchan-@BACKEND_VMM@-devel
 BuildRequires: pam-devel
+%if 0%{?rhel} >= 7
+BuildRequires: python-setuptools
+%endif
+BuildRequires: systemd
+Source0: %{name}-%{version}.tar.gz
 
 %description
 The Qubes core files for installation inside a Qubes VM.
@@ -174,29 +172,15 @@ DNF plugin for Qubes specific post-installation actions:
  * notify dom0 that updates were installed
  * refresh applications shortcut list
 
-%if 0%{fedora} >= 23
-%package -n python3-dnf-plugins-qubes-hooks
+%package -n python%{python3_pkgversion}-dnf-plugins-qubes-hooks
 Summary:        DNF plugin for Qubes specific post-installation actions
-BuildRequires: python3-devel
-%{?python_provide:%python_provide python3-dnf-plugins-qubes-hooks}
+BuildRequires: python%{python3_pkgversion}-devel
+%{?python_provide:%python_provide python%{python3_pkgversion}-dnf-plugins-qubes-hooks}
 
-%description -n python3-dnf-plugins-qubes-hooks
+%description -n python%{python3_pkgversion}-dnf-plugins-qubes-hooks
 DNF plugin for Qubes specific post-installation actions:
  * notify dom0 that updates were installed
  * refresh applications shortcut list
-%endif
-
-%if 0%{?rhel} >= 7
-%package -n python34-dnf-plugins-qubes-hooks
-Summary:        DNF plugin for Qubes specific post-installation actions
-BuildRequires: python34-devel
-%{?python_provide:%python_provide python34-dnf-plugins-qubes-hooks}
-
-%description -n python34-dnf-plugins-qubes-hooks
-DNF plugin for Qubes specific post-installation actions:
- * notify dom0 that updates were installed
- * refresh applications shortcut list
-%endif
 
 %package qrexec
 Summary:    Qubes qrexec agent
@@ -277,21 +261,15 @@ Requires: Thunar
 %description thunar
 Thunar support for Qubes VM tools
 
-%define _builddir %(pwd)
-
 %define kde_service_dir /usr/share/kde4/services
 %define kde5_service_dir /usr/share/kservices5/ServiceMenus
 
 %prep
-# we operate on the current directory, so no need to unpack anything
-# symlink is to generate useful debuginfo packages
-rm -f %{name}-%{version}
-ln -sf . %{name}-%{version}
-%setup -T -D
+%setup -q
 
 %build
 for dir in qubes-rpc qrexec misc; do
-  (cd $dir; make)
+  make -C $dir BACKEND_VMM=@BACKEND_VMM@
 done
 make -C doc manpages
 
@@ -353,6 +331,7 @@ for F in plymouth-shutdown prefdm splash-manager start-ttys tty ; do
 	fi
 done
 
+chgrp user /var/lib/qubes/dom0-updates
 
 # Remove old firmware updates link
 if [ -L /lib/firmware/updates ]; then
@@ -365,6 +344,12 @@ if test -f /etc/yum.conf && ! grep -q '/etc/yum\.conf\.d/qubes-proxy\.conf' /etc
   echo 'include=file:///etc/yum.conf.d/qubes-proxy.conf' >> /etc/yum.conf
 fi
 
+if ! [ -r /etc/dconf/profile/user ]; then
+    mkdir -p /etc/dconf/profile
+    echo "user-db:user" >> /etc/dconf/profile/user
+    echo "system-db:local" >> /etc/dconf/profile/user
+fi
+
 dconf update &> /dev/null || :
 
 # And actually setup the proxy usage in package managers
@@ -471,7 +456,7 @@ sed 's/^net.ipv4.ip_forward.*/#\0/'  -i /etc/sysctl.conf
 %post qrexec
 %systemd_post qubes-qrexec-agent.service
 
-%post thunar 
+%post thunar
 if [ "$1" = 1 ]; then
   # There is no system-wide Thunar custom actions. There is only a default
   # file and a user file created from the default one. Qubes actions need
@@ -508,7 +493,7 @@ fi
 %preun qrexec
 %systemd_preun qubes-qrexec-agent.service
 
-%postun thunar 
+%postun thunar
 if [ "$1" = 0 ]; then
   if [ -f /etc/xdg/Thunar/uca.xml ] ; then
     mv /etc/xdg/Thunar/uca.xml{,.uninstall}
@@ -609,10 +594,7 @@ rm -f %{name}-%{version}
 %config(noreplace) /etc/yum.repos.d/qubes-r4.repo
 /etc/yum/pluginconf.d/yum-qubes-hooks.conf
 %config(noreplace) /etc/dnf/plugins/qubes-hooks.conf
-%if 0%{?fedora} >= 23
-%config(noreplace) /etc/dconf/profile/user
 %config(noreplace) /etc/dconf/db/local.d/dpi
-%endif
 /usr/lib/systemd/system/user@.service.d/90-session-stop-timeout.conf
 /usr/sbin/qubes-serial-login
 /usr/bin/qvm-copy-to-vm
@@ -626,6 +608,7 @@ rm -f %{name}-%{version}
 /usr/bin/qvm-sync-clock
 /usr/bin/xenstore-watch-qubes
 /usr/bin/qubes-desktop-run
+/usr/bin/qubes-run-terminal
 /usr/bin/qubes-open
 /usr/bin/qubes-session-autostart
 %dir /usr/lib/qubes
@@ -664,7 +647,7 @@ rm -f %{name}-%{version}
 /usr/lib/qubes/init/functions
 %dir /usr/lib/qubes-bind-dirs.d
 /usr/lib/qubes-bind-dirs.d/30_cron.conf
-/usr/lib/python2.7/site-packages/qubesxdg.py*
+/usr/share/applications/qubes-run-terminal.desktop
 /usr/share/qubes/serial.conf
 /usr/share/glib-2.0/schemas/20_org.gnome.settings-daemon.plugins.updates.qubes.gschema.override
 /usr/share/glib-2.0/schemas/20_org.gnome.nautilus.qubes.gschema.override
@@ -678,6 +661,7 @@ rm -f %{name}-%{version}
 %{python_sitelib}/qubesagent/__init__.py*
 %{python_sitelib}/qubesagent/firewall.py*
 %{python_sitelib}/qubesagent/test_firewall.py*
+%{python_sitelib}/qubesagent/xdg.py*
 
 /usr/share/qubes/mime-override/globs
 /usr/share/qubes/qubes-master-key.asc
@@ -687,7 +671,7 @@ rm -f %{name}-%{version}
 %files -n python2-dnf-plugins-qubes-hooks
 %{python2_sitelib}/dnf-plugins/*
 
-%if 0%{fedora} >= 23
+%if 0%{?fedora} >= 23
 %files -n python3-dnf-plugins-qubes-hooks
 %{python3_sitelib}/dnf-plugins/*
 %endif
@@ -817,9 +801,6 @@ for svc in %qubes_services ; do
     fi
 done
 
-# dropped services
-chkconfig qubes-netwatcher off || :
-
 # TODO: make this not display the silly message about security context...
 sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab
 
@@ -967,3 +948,6 @@ if [ "x$changed" != "x" ]
 then
     systemctl daemon-reload
 fi
+
+%changelog
+@CHANGELOG@

+ 1 - 1
run-tests

@@ -10,4 +10,4 @@ export PYTHONPATH
 [ -r version ] || ln -s ${ROOTDIR}/version ./
 [ -r setup.py ] || ln -s ${ROOTDIR}/setup.py ./
 "${PYTHON}" ./setup.py egg_info --egg-base "${TESTPYTHONPATH}"
-"${PYTHON}" -m coverage run -m unittest discover -p '*.py' -v "$@"
+"${PYTHON}" -m coverage run -m unittest discover -p 'test_*.py' -v "$@"

+ 1 - 1
version

@@ -1 +1 @@
-4.0.23
+4.0.31

+ 1 - 0
vm-systemd/75-qubes-vm.preset

@@ -56,6 +56,7 @@ disable smartd.service
 disable upower.service
 disable colord.service
 disable wpa_supplicant@.service
+disable dkms.service
 
 # Fedora only services
 disable cpuspeed.service

+ 1 - 0
vm-systemd/qubes-firewall.service

@@ -5,6 +5,7 @@ After=qubes-iptables.service
 Before=qubes-network.service
 
 [Service]
+Type=notify
 ExecStart=/usr/sbin/qubes-firewall
 
 [Install]

+ 1 - 1
vm-systemd/qubes-misc-post.service

@@ -1,6 +1,6 @@
 [Unit]
 Description=Qubes misc post-boot actions
-After=network-pre.target qubes-mount-dirs.service qubes-network.service qubes-firewall.service qubes-netwatcher.service
+After=network-pre.target qubes-mount-dirs.service qubes-network.service qubes-firewall.service
 
 [Service]
 Type=oneshot