Merge branch 'view-only'

* view-only:
  Add file managers integration for qvm-open-in-dvm --view-only
  qvm-open-in-vm: mark file as read-only if opened with --view-only
  qvm-open-in-vm: implement --view-only option
  qubes-rpc: fix code style - indent with spaces
This commit is contained in:
Marek Marczykowski-Górecki 2018-05-26 22:41:05 +02:00
commit bd445742fb
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
12 changed files with 535 additions and 441 deletions

View File

@ -59,7 +59,7 @@
</action> </action>
<action> <action>
<icon>document-open</icon> <icon>document-open</icon>
<name>Open in DisposableVM</name> <name>Edit in DisposableVM</name>
<unique-id>1507455559234996-8</unique-id> <unique-id>1507455559234996-8</unique-id>
<command>/usr/lib/qubes/qvm-actions.sh opendvm %F</command> <command>/usr/lib/qubes/qvm-actions.sh opendvm %F</command>
<description></description> <description></description>
@ -70,3 +70,16 @@
<text-files/> <text-files/>
<video-files/> <video-files/>
</action> </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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,6 +45,12 @@ case "$action" in
qvm-open-in-dvm "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 & qvm-open-in-dvm "$file" | zenity --notification --text "Opening $file in DisposableVM..." --timeout 3 &
done 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..." echo "Unknown action. Aborting..."
exit 1 exit 1

View File

@ -1,10 +1,15 @@
[Desktop Entry] [Desktop Entry]
Actions=QvmDvm; Actions=QvmDvm;QvmViewDvm
Type=Service Type=Service
X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles
[Desktop Action QvmDvm] [Desktop Action QvmDvm]
Exec=/usr/bin/qvm-open-in-dvm %U Exec=/usr/bin/qvm-open-in-dvm %U
Icon=kget 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

View File

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

View File

@ -20,16 +20,37 @@
# #
# #
if ! [ $# = 2 ] ; then usage() {
echo "Usage: $0 vmname filename" echo "Usage: $0 [--view-only] vmname filename"
exit 1 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 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 esac

View File

@ -17,15 +17,24 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
if not files: if not files:
return return
menu_item = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm', menu_item1 = Nautilus.MenuItem(name='QubesMenuProvider::OpenInDvm',
label='Open In DisposableVM', label='Edit In DisposableVM',
tip='', tip='',
icon='') icon='')
menu_item.connect('activate', self.on_menu_item_clicked, files) menu_item1.connect('activate', self.on_menu_item_clicked, files)
return menu_item,
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. '''Called when user chooses files though Nautilus context menu.
''' '''
for file_obj in files: for file_obj in files:
@ -38,6 +47,11 @@ class OpenInDvmItemExtension(GObject.GObject, Nautilus.MenuProvider):
# Use subprocess.DEVNULL in python >= 3.3 # Use subprocess.DEVNULL in python >= 3.3
devnull = open(os.devnull, 'wb') 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 # 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()

View File

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