diff --git a/debian/qubes-core-agent.install b/debian/qubes-core-agent.install index 5debdca..3f5f558 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/qfile-unpacker.c b/qubes-rpc/qfile-unpacker.c index 5d16121..aeff009 100644 --- a/qubes-rpc/qfile-unpacker.c +++ b/qubes-rpc/qfile-unpacker.c @@ -36,7 +36,7 @@ char *prepare_creds_return_dir(int uid) return pwd->pw_dir; } -int main(int argc __attribute((__unused__)), char ** argv __attribute__((__unused__))) +int main(int argc, char ** argv) { char *home_dir; char *incoming_dir_root; @@ -46,22 +46,45 @@ int main(int argc __attribute((__unused__)), char ** argv __attribute__((__unuse const char *remote_domain; char *procdir_path; int procfs_fd; + int i; - uid = getuid(); - home_dir = prepare_creds_return_dir(uid); + if (argc >= 3) { + errno = 0; + uid = strtol(argv[1], NULL, 10); + if (errno) + gui_fatal("Invalid user ID argument"); + home_dir = prepare_creds_return_dir(uid); + incoming_dir = argv[2]; + } else { + uid = getuid(); + home_dir = prepare_creds_return_dir(uid); + remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); + if (!remote_domain) { + gui_fatal("Cannot get remote domain name"); + } - remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); - if (!remote_domain) { - gui_fatal("Cannot get remote domain name"); + if (asprintf(&incoming_dir_root, "%s/%s", home_dir, INCOMING_DIR_NAME) < 0) { + gui_fatal("Error allocating memory"); + } + 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 (asprintf(&incoming_dir_root, "%s/%s", home_dir, INCOMING_DIR_NAME) < 0) { - gui_fatal("Error allocating memory"); + for (i = 3; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0) + set_verbose(1); + else if (strcmp(argv[i], "-w") == 0) + if (i+1 < argc && argv[i+1][0] != '-') { + set_wait_for_space(atoi(argv[i+1])); + i++; + } else + set_wait_for_space(1); + else + gui_fatal("Invalid option %s", argv[i]); } - 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); 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/qubes-rpc/tar2qfile.c b/qubes-rpc/tar2qfile.c index b0786d2..c05e130 100644 --- a/qubes-rpc/tar2qfile.c +++ b/qubes-rpc/tar2qfile.c @@ -957,7 +957,6 @@ void tar_file_processor(int fd, struct filters *filters) int main(int argc, char **argv) { - int i; char *entry; int fd = -1; int use_stdin = 1; @@ -967,18 +966,15 @@ int main(int argc, char **argv) /* when extracting backup header, dom0 will terminate the transfer with * EDQUOT just after getting qubes.xml */ set_ignore_quota_error(1); - for (i = 1; i < argc; i++) { - set_nonblock(0); - if (strcmp(argv[i], "-")==0) { + set_nonblock(0); + if (argc > 1) { + if (strcmp(argv[1], "-")==0) { use_stdin = 1; - i++; - break; } else { - // Parse tar file use_stdin = 0; - entry = argv[i]; + entry = argv[1]; #ifdef DEBUG - fprintf(stderr,"Parsing file %s\n",entry); + fprintf(stderr, "Parsing file %s\n",entry); #endif fd = open(entry, O_RDONLY); @@ -986,12 +982,14 @@ int main(int argc, char **argv) fprintf(stderr,"Error opening file %s\n",entry); exit(2); } - i++; - break; } } - filters.filters_count = argc-i; - filters.filters = argv+i; + // Parse tar file + if (argc > 2) + filters.filters_count = argc-2; + else + filters.filters_count = 0; + filters.filters = argv+2; filters.filters_matches = calloc(filters.filters_count, sizeof(int)); if (filters.filters_matches == NULL) { perror("calloc"); diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index 2cc0e98..90125da 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