diff --git a/debian/qubes-core-agent.install b/debian/qubes-core-agent.install index fa4f033..27040aa 100644 --- a/debian/qubes-core-agent.install +++ b/debian/qubes-core-agent.install @@ -18,7 +18,9 @@ etc/qubes-rpc/qubes.InstallUpdatesGUI etc/qubes-rpc/qubes.OpenInVM etc/qubes-rpc/qubes.OpenURL etc/qubes-rpc/qubes.PostInstall +etc/qubes-rpc/qubes.RegisterBackupLocation etc/qubes-rpc/qubes.ResizeDisk +etc/qubes-rpc/qubes.RestoreById etc/qubes-rpc/qubes.Restore etc/qubes-rpc/qubes.SelectDirectory etc/qubes-rpc/qubes.SelectFile diff --git a/qubes-rpc/Makefile b/qubes-rpc/Makefile index 493a24c..67648a8 100644 --- a/qubes-rpc/Makefile +++ b/qubes-rpc/Makefile @@ -60,6 +60,8 @@ install: qubes.WaitForSession \ qubes.DetachPciDevice \ qubes.Backup qubes.Restore \ + qubes.RegisterBackupLocation \ + qubes.RestoreById \ qubes.SelectFile qubes.SelectDirectory \ qubes.GetImageRGBA \ qubes.SetDateTime \ diff --git a/qubes-rpc/qubes.RegisterBackupLocation b/qubes-rpc/qubes.RegisterBackupLocation new file mode 100644 index 0000000..215faf5 --- /dev/null +++ b/qubes-rpc/qubes.RegisterBackupLocation @@ -0,0 +1,33 @@ +#!/bin/sh + +# Register backup location (path or a command) to be retrieved with qubes +# .RestoreById service. +# Registered location is only valid as long as this service call stays open + +set -e + +REGISTRY_DIR="$XDG_RUNTIME_DIR/qubes-backup-location" + +if ! [ -d "$REGISTRY_DIR" ]; then + mkdir -p "$REGISTRY_DIR" +fi + +read -r backup_location + +REGISTRY_FILE=$(mktemp "$REGISTRY_DIR/XXXXXXXX") + +PID=$$ +# this isn't perfetct, as comm field could contain spaces, but we do control +# this value and we know it doesn't +START_TIME=$(cut -f 22 -d ' ' /proc/$PID/stat) +# add process id at the beginning to help verifying if it's still running; +# record starttime too, to detect PID reuse +printf "%d %d\n%s\n" "$PID" "$START_TIME" "$backup_location" >"$REGISTRY_FILE" +# output registered ID to the user +basename "$REGISTRY_FILE" +# close stdout +exec >&- +# wait for stdin to close +cat >/dev/null +# and cleanup +rm -f "$REGISTRY_FILE" \ No newline at end of file diff --git a/qubes-rpc/qubes.RestoreById b/qubes-rpc/qubes.RestoreById new file mode 100755 index 0000000..fcbe4fa --- /dev/null +++ b/qubes-rpc/qubes.RestoreById @@ -0,0 +1,82 @@ +#!/bin/sh + +set -e + +REGISTRY_DIR="$XDG_RUNTIME_DIR/qubes-backup-location" + +backup_location_id="$1" + +if [ -z "$backup_location_id" ]; then + echo "Missing backup location ID argument" >&2 + exit 1 +fi + +if ! [ -e "$REGISTRY_DIR/$backup_location_id" ]; then + echo "Invalid location ID" >&2 + exit 1 +fi + +while true; do + read -r check_pid check_starttime + read -r backup_location + break +done < "$REGISTRY_DIR/$backup_location_id" + +if ! [ -e "/proc/$check_pid" ]; then + echo "Invalid location ID" >&2 + exit 1 +fi + +pid_starttime=$(cut -f 22 -d ' ' "/proc/$check_pid/stat") +if [ "$check_starttime" != "$pid_starttime" ]; then + echo "Invalid location ID" >&2 + exit 1 +fi + +# now $backup_location is verified to be still valid + +echo Starting Restorecopy >&2 +read -r untrusted_paths +echo "Backup location: $backup_location" >&2 +echo "Paths: $untrusted_paths" >&2 +if [ -f "$backup_location" ] ; then + echo "Performing restore from backup file $backup_location" >&2 + TARGET="$backup_location" + echo "Copying $TARGET to STDOUT" >&2 + # tar2qfile always use argv[1] for input path and the rest for selecting + # paths to extract - no other options are supported, so passing + # untrusted_paths directly is fine + # shellcheck disable=SC2086 + /usr/lib/qubes/tar2qfile "$TARGET" $untrusted_paths +else + echo "Checking if arguments is matching a command" >&2 + COMMAND=$(echo "$backup_location" | cut -d ' ' -f 1) + if command -v "$COMMAND" >/dev/null; then + tmpdir=$(mktemp -d) + mkfifo "$tmpdir/backup-data" + echo "Redirecting $backup_location to STDOUT" >&2 + # Parsing args to handle quotes correctly + # Dangerous method if args are uncontrolled + eval "set -- $backup_location" + # Use named pipe to pass original stdin to tar2file + "$@" > "$tmpdir/backup-data" < /dev/null & + # shellcheck disable=SC2086 + # tar2qfile always use argv[1] for input path and the rest for selecting + # paths to extract - no other options are supported, so passing + # untrusted_paths directly is fine + /usr/lib/qubes/tar2qfile "$tmpdir/backup-data" $untrusted_paths + # Restoration may be terminated earlier because of selected files. This + # will be seen as EPIPE to the retrieving process, which may cause retcode + # other than 0 in some cases - which would be incorrectly treated as backup + # restore error. So instead of that, use tar2qfile exit code (and have dom0 + # detect if anything wrong with actual data) + retcode=$? + wait $! + rm "$tmpdir/backup-data" + rmdir "$tmpdir" + exit "$retcode" + else + echo "Invalid command $COMMAND" >&2 + exit 2 + fi +fi diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index f4dd797..c3298a8 100644 --- a/rpm_spec/core-agent.spec.in +++ b/rpm_spec/core-agent.spec.in @@ -590,6 +590,8 @@ rm -f %{name}-%{version} %config(noreplace) /etc/qubes-rpc/qubes.DetachPciDevice %config(noreplace) /etc/qubes-rpc/qubes.Backup %config(noreplace) /etc/qubes-rpc/qubes.Restore +%config(noreplace) /etc/qubes-rpc/qubes.RegisterBackupLocation +%config(noreplace) /etc/qubes-rpc/qubes.RestoreById %config(noreplace) /etc/qubes-rpc/qubes.SelectFile %config(noreplace) /etc/qubes-rpc/qubes.SelectDirectory %config(noreplace) /etc/qubes-rpc/qubes.GetImageRGBA