diff --git a/appvm/Makefile b/appvm/Makefile
index c6f4d256..c1abcfad 100644
--- a/appvm/Makefile
+++ b/appvm/Makefile
@@ -1,12 +1,14 @@
 CC=gcc
 CFLAGS=-Wall -I../common
-all:	qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent
+all:	qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm dvm_file_editor qfile-agent-dvm qfile-agent qfile-unpacker
 dvm_file_editor: dvm_file_editor.o ../common/ioall.o
 	$(CC) -o dvm_file_editor dvm_file_editor.o ../common/ioall.o
 qfile-agent-dvm: qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o
 	$(CC) -o qfile-agent-dvm qfile-agent-dvm.o ../common/ioall.o ../common/gui-fatal.o 
 qfile-agent: qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o
 	$(CC) -o qfile-agent qfile-agent.o ../common/ioall.o ../common/gui-fatal.o copy_file.o
+qfile-unpacker: qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o
+	$(CC) -o qfile-unpacker qfile-unpacker.o ../common/ioall.o ../common/gui-fatal.o copy_file.o unpack.o
 qubes_penctl: qubes_penctl.o
 	$(CC) -o qubes_penctl qubes_penctl.o -lxenstore
 qubes_add_pendrive_script: qubes_add_pendrive_script.o
diff --git a/appvm/qfile-unpacker.c b/appvm/qfile-unpacker.c
new file mode 100644
index 00000000..eaa5c067
--- /dev/null
+++ b/appvm/qfile-unpacker.c
@@ -0,0 +1,83 @@
+#define _GNU_SOURCE
+#include <ioall.h>
+#include <grp.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/fsuid.h>
+#include <gui-fatal.h>
+#include <errno.h>
+#include "filecopy.h"
+#define INCOMING_DIR_ROOT "/home/user/incoming"
+int prepare_creds_return_uid(char *username)
+{
+	struct passwd *pwd;
+	pwd = getpwnam(username);
+	if (!pwd) {
+		perror("getpwnam");
+		exit(1);
+	}
+	setenv("HOME", pwd->pw_dir, 1);
+	setenv("USER", username, 1);
+	setgid(pwd->pw_gid);
+	initgroups(username, pwd->pw_gid);
+	setfsuid(pwd->pw_uid);
+	return pwd->pw_uid;
+}
+
+void wait_for_child(int statusfd)
+{
+	int status;
+	if (read(statusfd, &status, sizeof status)!=sizeof status)
+		gui_fatal("File copy error: Internal error reading status from unpacker");
+	errno = status;
+	switch (status) {
+	case LEGAL_EOF: break;
+	case 0: gui_fatal("File copy: Connection terminated unexpectedly"); break;
+	case EINVAL: gui_fatal("File copy: Corrupted data from packer"); break;
+	case EEXIST: gui_fatal("File copy: not overwriting existing file. Clean ~/incoming, and retry copy"); break;
+	default: gui_fatal("File copy"); 
+	}
+}
+
+extern void do_unpack(int);
+
+int main(int argc, char ** argv)
+{
+	char *incoming_dir;
+	int pipefds[2];
+	int uid;
+
+	pipe(pipefds);
+
+	uid = prepare_creds_return_uid("user");
+
+	mkdir(INCOMING_DIR_ROOT, 0700);
+	asprintf(&incoming_dir, "%s/from-%s", INCOMING_DIR_ROOT, argv[1]);
+	mkdir(incoming_dir, 0700);
+	if (chdir(incoming_dir))
+		gui_fatal("Error chdir to %s", incoming_dir); 
+	switch (fork()) {
+	case -1:
+		perror("fork");
+		exit(1);
+	case 0:
+		if (chroot(incoming_dir)) //impossible
+			gui_fatal("Error chroot to %s", incoming_dir);
+		setuid(uid);
+		close(pipefds[0]);
+		do_unpack(pipefds[1]);
+		exit(0);
+	default:;
+	}
+
+	setuid(uid);
+	close(pipefds[1]);
+	wait_for_child(pipefds[0]);
+
+	return 0;
+}
diff --git a/appvm/unpack.c b/appvm/unpack.c
new file mode 100644
index 00000000..c0353c11
--- /dev/null
+++ b/appvm/unpack.c
@@ -0,0 +1,101 @@
+#include <errno.h>
+#include <ioall.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "filecopy.h"
+
+char namebuf[MAX_PATH_LENGTH];
+void notify_progress(int p1, int p2)
+{
+}
+
+int global_status_fd;
+void do_exit(int code)
+{
+	int codebuf = code;
+	write(global_status_fd, &codebuf, sizeof codebuf);
+	exit(0);
+}
+
+
+void fix_times_and_perms(struct file_header *hdr, char *name)
+{
+	struct timeval times[2] =
+	    { {hdr->atime, hdr->atime_nsec / 1000}, {hdr->mtime,
+						     hdr->mtime_nsec / 1000}
+	};
+	if (chmod(name, hdr->mode & 07777))
+		do_exit(errno);
+	if (utimes(name, times))
+		do_exit(errno);
+}
+
+
+
+void process_one_file_reg(struct file_header *hdr, char *name)
+{
+	char *ret;
+	int fdout =
+	    open(name, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0700);
+	if (fdout < 0)
+		do_exit(errno);
+	ret = copy_file(fdout, 0, hdr->filelen);
+	if (ret)
+		do_exit(errno);
+	close(fdout);
+	fix_times_and_perms(hdr, name);
+}
+
+
+void process_one_file_dir(struct file_header *hdr, char *name)
+{
+	if (mkdir(name, 0700) && errno != EEXIST)
+		do_exit(errno);
+	fix_times_and_perms(hdr, name);
+}
+
+void process_one_file_link(struct file_header *hdr, char *name)
+{
+	char content[MAX_PATH_LENGTH];
+	if (hdr->filelen > MAX_PATH_LENGTH - 1)
+		do_exit(ENAMETOOLONG);
+	if (!read_all(0, content, hdr->filelen))
+		do_exit(errno);
+	content[hdr->filelen] = 0;
+	if (symlink(content, name))
+		do_exit(errno);
+
+}
+
+void process_one_file(struct file_header *hdr)
+{
+	if (hdr->namelen > MAX_PATH_LENGTH - 1)
+		do_exit(ENAMETOOLONG);
+	if (!read_all(0, namebuf, hdr->namelen))
+		do_exit(errno);
+	namebuf[hdr->namelen] = 0;
+	if (S_ISREG(hdr->mode))
+		process_one_file_reg(hdr, namebuf);
+	else if (S_ISLNK(hdr->mode))
+		process_one_file_link(hdr, namebuf);
+	else if (S_ISDIR(hdr->mode))
+		process_one_file_dir(hdr, namebuf);
+	else
+		do_exit(EINVAL);
+}
+
+void do_unpack(int fd)
+{
+	global_status_fd = fd;
+	struct file_header hdr;
+	while (read_all(0, &hdr, sizeof hdr))
+		process_one_file(&hdr);
+	if (errno)
+		do_exit(errno);
+	else
+		do_exit(LEGAL_EOF);
+}
diff --git a/dom0/restore/qfile-daemon b/dom0/restore/qfile-daemon
new file mode 100644
index 00000000..6b589279
--- /dev/null
+++ b/dom0/restore/qfile-daemon
@@ -0,0 +1,59 @@
+#!/usr/bin/python2.6
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2010  Rafal Wojtczuk  <rafal@invisiblethingslab.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+#
+#
+import os
+import sys
+import subprocess
+from qubes.qubes import QubesVmCollection
+
+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 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()
+
+    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)
+
+main()
diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec
index 3023ffa0..f3c875d4 100644
--- a/rpm_spec/core-appvm.spec
+++ b/rpm_spec/core-appvm.spec
@@ -76,7 +76,7 @@ cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_RO
 mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes
 cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm.kde $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 $RPM_BUILD_ROOT/usr/lib/qubes
+cp dvm_file_editor qfile-agent qfile-agent-dvm qfile-unpacker $RPM_BUILD_ROOT/usr/lib/qubes
 ln -s /usr/bin/qvm-open-in-dvm $RPM_BUILD_ROOT/usr/lib/qubes/qvm-dvm-transfer 
 cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes
 mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir}
@@ -218,6 +218,7 @@ rm -rf $RPM_BUILD_ROOT
 /usr/lib/qubes/qrexec_agent
 /usr/lib/qubes/qfile-agent
 /usr/lib/qubes/qfile-agent-dvm
+/usr/lib/qubes/qfile-unpacker
 /etc/udev/rules.d/qubes.rules
 /etc/sysconfig/iptables
 /var/lib/qubes
diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec
index 89cbd284..ed72a347 100644
--- a/rpm_spec/core-dom0.spec
+++ b/rpm_spec/core-dom0.spec
@@ -96,6 +96,7 @@ cp restore/xenstore-watch restore/qvm-create-default-dvm $RPM_BUILD_ROOT/usr/bin
 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/var/lib/qubes
 mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/vm-templates
@@ -265,6 +266,7 @@ fi
 /usr/lib/qubes/qmemman_daemon.py*
 /usr/lib/qubes/meminfo-writer
 /usr/lib/qubes/qfile-daemon-dvm*
+/usr/lib/qubes/qfile-daemon
 %attr(770,root,qubes) %dir /var/lib/qubes
 %attr(770,root,qubes) %dir /var/lib/qubes/vm-templates
 %attr(770,root,qubes) %dir /var/lib/qubes/appvms