42b1355957
This will cause most applications to disallow changing the file and also add some visual indication about the view being read only. This will avoid making the changes that would be discarded later. QubesOS/qubes-issues#1118
246 lines
6.9 KiB
C
246 lines
6.9 KiB
C
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/time.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <libqubes-rpc-filecopy.h>
|
|
#include "dvm2.h"
|
|
|
|
#define USER_HOME "/home/user"
|
|
#define TMP_LOC "/tmp/qopen/"
|
|
// #define DEBUG
|
|
|
|
static const char *cleanup_filename = NULL;
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 *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;
|
|
|
|
fail:
|
|
fprintf(stderr, "Invalid remote domain name: %s\n", remote_domain);
|
|
exit(1);
|
|
}
|
|
|
|
char *get_filename(int *view_only)
|
|
{
|
|
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))
|
|
exit(1);
|
|
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
|
|
main()
|
|
{
|
|
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());
|
|
#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));
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
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];
|
|
#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);
|
|
#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);
|
|
#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;
|
|
}
|