diff --git a/Makefile b/Makefile index b38c5f5c..70349959 100644 --- a/Makefile +++ b/Makefile @@ -33,3 +33,5 @@ clean: (cd dom0/restore && make clean) (cd dom0/qmemman && make clean) (cd common && make clean) + make -C qrexec clean + make -C vchan clean diff --git a/appvm/Makefile b/appvm/Makefile index 858a665b..f95b1abd 100644 --- a/appvm/Makefile +++ b/appvm/Makefile @@ -1,6 +1,14 @@ CC=gcc -CFLAGS=-Wall -all: qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm +CFLAGS=-g -Wall -I../common +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) -g -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) -g -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) -g -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) -g -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 @@ -9,4 +17,5 @@ qvm-open-in-dvm: qvm-open-in-dvm.o $(CC) -o qvm-open-in-dvm qvm-open-in-dvm.o -lxenstore clean: - rm -f qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm xenstore-watch *.o *~ + rm -f qfile-agent-dvm qfile-agent qfile-unpacker dvm_file_editor qubes_penctl qubes_add_pendrive_script qvm-open-in-dvm *.o *~ + rm -f xenstore-watch diff --git a/appvm/copy_file.c b/appvm/copy_file.c new file mode 100644 index 00000000..5f7fc793 --- /dev/null +++ b/appvm/copy_file.c @@ -0,0 +1,28 @@ +#include +#include +extern void notify_progress(int, int); + +char * copy_file(int outfd, int infd, long long size) +{ + char buf[4096]; + long long written = 0; + int ret; + int count; + while (written < size) { + if (size - written > sizeof(buf)) + count = sizeof buf; + else + count = size - written; + ret = read(infd, buf, count); + if (!ret) + return("EOF while reading file"); + if (ret < 0) + return("error reading file"); + if (!write_all(outfd, buf, ret)) + return("error writing file content"); + notify_progress(ret, 0); + written += ret; + } + return NULL; +} + diff --git a/appvm/dvm2.h b/appvm/dvm2.h new file mode 100644 index 00000000..0e5922cd --- /dev/null +++ b/appvm/dvm2.h @@ -0,0 +1,2 @@ +#define DVM_FILENAME_SIZE 256 +#define DVM_SPOOL "/home/user/.dvmspool" diff --git a/appvm/dvm_file_editor.c b/appvm/dvm_file_editor.c new file mode 100644 index 00000000..4eed30ba --- /dev/null +++ b/appvm/dvm_file_editor.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#include +#include "dvm2.h" + +char *get_filename() +{ + char buf[DVM_FILENAME_SIZE]; + static char retname[sizeof(buf) + sizeof("/tmp/")]; + if (!read_all(0, buf, sizeof(buf))) + exit(1); + if (index(buf, '/')) { + fprintf(stderr, "filename contains /"); + exit(1); + } + snprintf(retname, sizeof(retname), "/tmp/%s", buf); + return retname; +} + +void copy_file(char *filename) +{ + int fd = open(filename, O_WRONLY | O_CREAT, 0600); + if (fd < 0) { + perror("open file"); + exit(1); + } + if (!copy_fd_all(fd, 0)) + exit(1); + close(fd); +} + +void send_file_back(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); +} + +int +main() +{ + char cmdbuf[512]; + struct stat stat_pre, stat_post; + char *filename = get_filename(); + + copy_file(filename); + if (stat(filename, &stat_pre)) { + perror("stat pre"); + exit(1); + } + snprintf(cmdbuf, sizeof(cmdbuf), + "HOME=/home/user DISPLAY=:0 /usr/bin/mimeopen -n -M '%s' 2>&1 > /tmp/kde-open.log +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dvm2.h" + +void send_file(char *fname) +{ + char *base; + int fd = open(fname, O_RDONLY); + if (fd < 0) + gui_fatal("open %s", fname); + base = rindex(fname, '/'); + if (!base) + base = fname; + else + base++; + if (strlen(base) >= DVM_FILENAME_SIZE) + base += strlen(base) - DVM_FILENAME_SIZE + 1; + if (!write_all(1, base, DVM_FILENAME_SIZE)) + gui_fatal("send filename to dispVM"); + if (!copy_fd_all(1, fd)) + gui_fatal("send file to dispVM"); + close(1); +} + +int copy_and_return_nonemptiness(int tmpfd) +{ + struct stat st; + if (!copy_fd_all(tmpfd, 0)) + gui_fatal("receiving file from dispVM"); + if (fstat(tmpfd, &st)) + gui_fatal("fstat"); + close(tmpfd); + + return st.st_size; +} + +void recv_file_nowrite(char *fname) +{ + char *tempfile; + char *errmsg; + int tmpfd; + + asprintf(&tempfile, "/tmp/file_edited_in_dvm.XXXXXX"); + tmpfd = mkstemp(tempfile); + if (tmpfd < 0) + gui_fatal("unable to create any temporary file, aborting"); + if (!copy_and_return_nonemptiness(tmpfd)) { + unlink(tempfile); + return; + } + asprintf(&errmsg, + "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 " + "saved to %s", fname, tempfile); + gui_nonfatal(errmsg); +} + +void actually_recv_file(char *fname, char *tempfile, int tmpfd) +{ + if (!copy_and_return_nonemptiness(tmpfd)) { + unlink(tempfile); + return; + } + if (rename(tempfile, fname)) + gui_fatal("rename"); +} + +void recv_file(char *fname) +{ + int tmpfd; + char *tempfile; + asprintf(&tempfile, "%s.XXXXXX", fname); + tmpfd = mkstemp(tempfile); + if (tmpfd < 0) + recv_file_nowrite(fname); + else + actually_recv_file(fname, tempfile, tmpfd); +} + +void talk_to_daemon(char *fname) +{ + send_file(fname); + recv_file(fname); +} + +void process_spoolentry(char *entry_name) +{ + char *abs_spool_entry_name; + int entry_fd; + struct stat st; + char *filename; + int entry_size; + asprintf(&abs_spool_entry_name, "%s/%s", DVM_SPOOL, entry_name); + entry_fd = open(abs_spool_entry_name, O_RDONLY); + unlink(abs_spool_entry_name); + if (entry_fd < 0 || fstat(entry_fd, &st)) + gui_fatal("bad dvm_entry"); + entry_size = st.st_size; + filename = calloc(1, entry_size + DVM_FILENAME_SIZE); + if (!filename) + gui_fatal("malloc"); + if (!read_all(entry_fd, filename, entry_size)) + gui_fatal("read dvm entry %s", abs_spool_entry_name); + close(entry_fd); + talk_to_daemon(filename); +} + +void scan_spool(char *name) +{ + struct dirent *ent; + DIR *dir = opendir(name); + if (!dir) + gui_fatal("opendir %s", name); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (!strcmp(fname, ".") || !strcmp(fname, "..")) + continue; + process_spoolentry(fname); + break; + } + closedir(dir); +} + +int main() +{ + signal(SIGPIPE, SIG_IGN); + scan_spool(DVM_SPOOL); + return 0; +} diff --git a/appvm/qfile-agent.c b/appvm/qfile-agent.c new file mode 100644 index 00000000..f7e27a98 --- /dev/null +++ b/appvm/qfile-agent.c @@ -0,0 +1,215 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filecopy.h" + +enum { + PROGRESS_FLAG_NORMAL, + PROGRESS_FLAG_INIT, + PROGRESS_FLAG_DONE +}; + + + +char *client_flags; +void do_notify_progress(long long total, int flag) +{ + FILE *progress; + if (!client_flags[0]) + return; + progress = fopen(client_flags, "w"); + if (!progress) + return; + fprintf(progress, "%d %lld %s", getpid(), total, + flag == PROGRESS_FLAG_DONE ? "DONE" : "BUSY"); + fclose(progress); +} + +void notify_progress(int size, int flag) +{ + static long long total = 0; + static long long prev_total = 0; + total += size; + if (total > prev_total + PROGRESS_NOTIFY_DELTA + || (flag != PROGRESS_FLAG_NORMAL)) { + do_notify_progress(total, flag); + prev_total = total; + } +} + +void write_headers(struct file_header *hdr, char *filename) +{ + if (!write_all(1, hdr, sizeof(*hdr)) + || !write_all(1, filename, hdr->namelen)) + gui_fatal("writing file headers to remove AppVM"); +} + +int single_file_processor(char *filename, struct stat *st) +{ + struct file_header hdr; + int fd; + mode_t mode = st->st_mode; + + hdr.namelen = strlen(filename) + 1; + hdr.mode = mode; + hdr.atime = st->st_atim.tv_sec; + hdr.atime_nsec = st->st_atim.tv_nsec; + hdr.mtime = st->st_mtim.tv_sec; + hdr.mtime_nsec = st->st_mtim.tv_nsec; + + if (S_ISREG(mode)) { + char *ret; + fd = open(filename, O_RDONLY); + if (!fd) + gui_fatal("open %s", filename); + hdr.filelen = st->st_size; + write_headers(&hdr, filename); + ret = copy_file(1, fd, hdr.filelen); + if (ret) + gui_fatal("Copying file %s: %s", filename, ret); + close(fd); + } + if (S_ISDIR(mode)) { + hdr.filelen = 0; + write_headers(&hdr, filename); + } + if (S_ISLNK(mode)) { + char name[st->st_size + 1]; + if (readlink(filename, name, sizeof(name)) != st->st_size) + gui_fatal("readlink %s", filename); + hdr.filelen = st->st_size + 1; + write_headers(&hdr, filename); + if (!write_all(1, name, st->st_size + 1)) + gui_fatal("write to remote VM"); + } + return 0; +} + +int do_fs_walk(char *file) +{ + char *newfile; + struct stat st; + struct dirent *ent; + DIR *dir; + + if (lstat(file, &st)) + gui_fatal("stat %s", file); + single_file_processor(file, &st); + if (!S_ISDIR(st.st_mode)) + return 0; + dir = opendir(file); + if (!dir) + gui_fatal("opendir %s", file); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (!strcmp(fname, ".") || !strcmp(fname, "..")) + continue; + asprintf(&newfile, "%s/%s", file, fname); + do_fs_walk(newfile); + free(newfile); + } + closedir(dir); + // directory metadata is resent; this makes the code simple, + // and the atime/mtime is set correctly at the second time + single_file_processor(file, &st); + return 0; +} + +void send_vmname(char *vmname) +{ + char buf[FILECOPY_VMNAME_SIZE]; + memset(buf, 0, sizeof(buf)); + strncat(buf, vmname, sizeof(buf) - 1); + if (!write_all(1, buf, sizeof buf)) + gui_fatal("writing vmname to remote VM"); +} + +char *get_item(char *data, char **current, int size) +{ + char *ret; + if ((unsigned long) *current >= (unsigned long) data + size) + return NULL; + ret = *current; + *current += strlen(ret) + 1; + return ret; +} + +void parse_entry(char *data, int datasize) +{ + char *current = data; + char *vmname, *entry, *sep; + vmname = get_item(data, ¤t, datasize); + client_flags = get_item(data, ¤t, datasize); + notify_progress(0, PROGRESS_FLAG_INIT); + send_vmname(vmname); + while ((entry = get_item(data, ¤t, datasize))) { + do { + sep = rindex(entry, '/'); + if (!sep) + gui_fatal + ("Internal error: nonabsolute filenames not allowed"); + *sep = 0; + } while (sep[1] == 0); + if (entry[0] == 0) + chdir("/"); + else if (chdir(entry)) + gui_fatal("chdir to %s", entry); + do_fs_walk(sep + 1); + } + notify_progress(0, PROGRESS_FLAG_DONE); +} + +void process_spoolentry(char *entry_name) +{ + char *abs_spool_entry_name; + int entry_fd; + struct stat st; + char *entry; + int entry_size; + asprintf(&abs_spool_entry_name, "%s/%s", FILECOPY_SPOOL, + entry_name); + entry_fd = open(abs_spool_entry_name, O_RDONLY); + unlink(abs_spool_entry_name); + if (entry_fd < 0 || fstat(entry_fd, &st)) + gui_fatal("bad file copy spool entry"); + entry_size = st.st_size; + entry = calloc(1, entry_size + 1); + if (!entry) + gui_fatal("malloc"); + if (!read_all(entry_fd, entry, entry_size)) + gui_fatal("read filecopy entry"); + close(entry_fd); + parse_entry(entry, entry_size); +} + +void scan_spool(char *name) +{ + struct dirent *ent; + DIR *dir = opendir(name); + if (!dir) + gui_fatal("opendir %s", name); + while ((ent = readdir(dir))) { + char *fname = ent->d_name; + if (fname[0] != '.') { + process_spoolentry(fname); + break; + } + } + closedir(dir); +} + +int main() +{ + signal(SIGPIPE, SIG_IGN); + scan_spool(FILECOPY_SPOOL); + return 0; +} 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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/qvm-copy-to-vm2 b/appvm/qvm-copy-to-vm2 new file mode 100755 index 00000000..56dcdef8 --- /dev/null +++ b/appvm/qvm-copy-to-vm2 @@ -0,0 +1,46 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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. +# +# + +if [ $# -lt 2 ] ; then + echo usage: $0 'vmname file [file]*' + exit 1 +fi + +FILECOPY_SPOOL=/home/user/.filecopyspool +if ! [ -e $FILECOPY_SPOOL ] ; then + mkdir $FILECOPY_SPOOL +fi + +REQ_FILE_TMP=$FILECOPY_SPOOL/.req.$$ +echo -ne "$1""\x00" > $REQ_FILE_TMP +echo -ne "$PROGRESS_FILE""\x00" >> $REQ_FILE_TMP + +shift +for FILE in "$@" ; do + if ! [ "X""${FILE:0:1}" = X/ ] ; then + FILE="$PWD"/"$FILE" + fi + echo -ne "$FILE""\x00" >> $REQ_FILE_TMP +done + +mv $REQ_FILE_TMP $FILECOPY_SPOOL/req.$$ +echo -n FCPR > /var/run/qubes/qrexec_agent diff --git a/appvm/qvm-copy-to-vm2.kde b/appvm/qvm-copy-to-vm2.kde new file mode 100755 index 00000000..879279be --- /dev/null +++ b/appvm/qvm-copy-to-vm2.kde @@ -0,0 +1,48 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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. +# +# + +VM=$(kdialog -inputbox "Enter the VM name to send files to:") +if [ X$VM = X ] ; then exit 0 ; fi + +SIZE=$(du -c "$@" | tail -1 | cut -f 1) +REF=$(kdialog --progressbar "Copy progress") +qdbus $REF org.freedesktop.DBus.Properties.Set "" maximum $SIZE + +export PROGRESS_FILE=$(mktemp) +qvm-copy-to-vm2 $VM "$@" +while ! [ -s $PROGRESS_FILE ] ; do + sleep 0.1 +done +while true ; do + read agentpid sentsize agentstatus < $PROGRESS_FILE + if ! [ -e /proc/$agentpid ] ; then break ; fi + if [ "x"$agentstatus = xdone ] ; then break ; fi + CURRSIZE=$(($sentsize/1024)) + qdbus $REF org.freedesktop.DBus.Properties.Set "" value $CURRSIZE + sleep 0.4 +done + +qdbus $REF close +rm -f $PROGRESS_FILE +if ! [ "x"$agentstatus = xDONE ] ; then + kdialog --sorry 'Abnormal file copy termination; see /var/log/qubes/qrexec.xid.log in dom0 for more details' +fi diff --git a/appvm/qvm-copy.desktop b/appvm/qvm-copy.desktop index 5795eb61..4d5e800f 100644 --- a/appvm/qvm-copy.desktop +++ b/appvm/qvm-copy.desktop @@ -4,7 +4,7 @@ Type=Service X-KDE-ServiceTypes=KonqPopupMenu/Plugin,inode/directory,all/allfiles [Desktop Action QvmCopy] -Exec=/usr/lib/qubes/qvm-copy-to-vm.kde %U +Exec=/usr/lib/qubes/qvm-copy-to-vm2.kde %U Icon=kget Name=Send To VM diff --git a/appvm/qvm-dvm.desktop b/appvm/qvm-dvm.desktop index a7f5ad77..67f9ea51 100644 --- a/appvm/qvm-dvm.desktop +++ b/appvm/qvm-dvm.desktop @@ -4,7 +4,7 @@ Type=Service X-KDE-ServiceTypes=KonqPopupMenu/Plugin,all/allfiles [Desktop Action QvmDvm] -Exec=/usr/bin/qvm-open-in-dvm disposable %U +Exec=/usr/bin/qvm-open-in-dvm2 %U Icon=kget Name=Open In DisposableVM diff --git a/appvm/qvm-open-in-dvm2 b/appvm/qvm-open-in-dvm2 new file mode 100755 index 00000000..eb0d4e2e --- /dev/null +++ b/appvm/qvm-open-in-dvm2 @@ -0,0 +1,40 @@ +#!/bin/bash +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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. +# +# + +if ! [ $# = 1 ] ; then + echo "Usage: $0 filename" + exit 1 +fi + +FILE="$1" +if ! [ "X""${FILE:0:1}" = X/ ] ; then + FILE="$PWD"/"$1" +fi + +DVMSPOOL=/home/user/.dvmspool +if ! [ -e $DVMSPOOL ] ; then + mkdir $DVMSPOOL || exit 1 +fi + +echo -n "$FILE" > $DVMSPOOL/req.$$ +echo -n DVMR > /var/run/qubes/qrexec_agent + diff --git a/appvm/unpack.c b/appvm/unpack.c new file mode 100644 index 00000000..ad53ebf5 --- /dev/null +++ b/appvm/unpack.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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) +{ +// fix perms only when the directory is sent for the second time +// it allows to transfer r.x directory contents, as we create it rwx initially + if (!mkdir(name, 0700)) + return; + if (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/common/gui-fatal.c b/common/gui-fatal.c new file mode 100644 index 00000000..ed2b3d47 --- /dev/null +++ b/common/gui-fatal.c @@ -0,0 +1,50 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static void fix_display() +{ + setenv("DISPLAY", ":0", 1); +} + +static void produce_message(char * type, const char *fmt, va_list args) +{ + char *kdialog_msg; + char buf[1024]; + (void) vsnprintf(buf, sizeof(buf), fmt, args); + asprintf(&kdialog_msg, "%s: %s: %s (error type: %s)", + program_invocation_short_name, type, buf, strerror(errno)); + fprintf(stderr, "%s", kdialog_msg); + switch (fork()) { + case -1: + exit(1); //what else + case 0: + fix_display(); + execlp("kdialog", "kdialog", "--sorry", kdialog_msg, NULL); + exit(1); + default:; + } +} + +void gui_fatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + produce_message("Fatal error", fmt, args); + va_end(args); + exit(1); +} + +void gui_nonfatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + produce_message("Information", fmt, args); + va_end(args); +} diff --git a/common/gui-fatal.h b/common/gui-fatal.h new file mode 100644 index 00000000..de9799f9 --- /dev/null +++ b/common/gui-fatal.h @@ -0,0 +1,2 @@ +void gui_fatal(const char *fmt, ...); +void gui_nonfatal(const char *fmt, ...); diff --git a/common/ioall.c b/common/ioall.c new file mode 100644 index 00000000..239f3333 --- /dev/null +++ b/common/ioall.c @@ -0,0 +1,97 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include + +void perror_wrapper(char * msg) +{ + int prev=errno; + perror(msg); + errno=prev; +} + + +int write_all(int fd, void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, (char *) buf + written, size - written); + if (ret == -1 && errno == EINTR) + continue; + if (ret <= 0) { + perror_wrapper("write"); + return 0; + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return 1; +} + +int read_all(int fd, void *buf, int size) +{ + int got_read = 0; + int ret; + while (got_read < size) { + ret = read(fd, (char *) buf + got_read, size - got_read); + if (ret == -1 && errno == EINTR) + continue; + if (ret == 0) { + errno = 0; + fprintf(stderr, "EOF\n"); + return 0; + } + if (ret < 0) { + perror_wrapper("read"); + return 0; + } + got_read += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return 1; +} + +int copy_fd_all(int fdout, int fdin) +{ + int ret; + char buf[4096]; + for (;;) { + ret = read(fdin, buf, sizeof(buf)); + if (ret == -1 && errno == EINTR) + continue; + if (!ret) + break; + if (ret < 0) { + perror_wrapper("read"); + return 0; + } + if (!write_all(fdout, buf, ret)) { + perror_wrapper("write"); + return 0; + } + } + return 1; +} diff --git a/common/ioall.h b/common/ioall.h new file mode 100644 index 00000000..1a700c6c --- /dev/null +++ b/common/ioall.h @@ -0,0 +1,3 @@ +int write_all(int fd, void *buf, int size); +int read_all(int fd, void *buf, int size); +int copy_fd_all(int fdout, int fdin); diff --git a/common/qubes_core b/common/qubes_core index 26fe514d..c20f7d71 100755 --- a/common/qubes_core +++ b/common/qubes_core @@ -54,6 +54,8 @@ start() fi fi + /usr/lib/qubes/qrexec_agent 2>/var/log/qubes/qrexec_agent.log & + [ -x /rw/config/rc.local ] && /rw/config/rc.local success echo "" diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 659609b8..421242f5 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1306,17 +1306,15 @@ class QubesProxyVm(QubesNetVm): iptables += "# '{0}' VM:\n".format(vm.name) iptables += "-A FORWARD ! -s {0}/32 -i vif{1}.0 -j DROP\n".format(vm.ip, xid) - accept_action = "ACCEPT" reject_action = "REJECT --reject-with icmp-host-prohibited" if conf["allow"]: - rules_action = accept_action - default_action = reject_action - iptables += "-A FORWARD -i vif{0}.0 -p icmp -j ACCEPT\n".format(xid) - else: - rules_action = reject_action default_action = accept_action + rules_action = reject_action + else: + default_action = reject_action + rules_action = accept_action for rule in conf["rules"]: iptables += "-A FORWARD -i vif{0}.0 -d {1}".format(xid, rule["address"]) @@ -1334,6 +1332,8 @@ class QubesProxyVm(QubesNetVm): # PREROUTING does DNAT to NetVM DNSes, so we need self.netvm_vm. properties iptables += "-A FORWARD -i vif{0}.0 -p udp -d {1} --dport 53 -j ACCEPT\n".format(xid,self.netvm_vm.gateway) iptables += "-A FORWARD -i vif{0}.0 -p udp -d {1} --dport 53 -j ACCEPT\n".format(xid,self.netvm_vm.secondary_dns) + if conf["allowIcmp"]: + iptables += "-A FORWARD -i vif{0}.0 -p icmp -j ACCEPT\n".format(xid) iptables += "-A FORWARD -i vif{0}.0 -j {1}\n".format(xid, default_action) @@ -1438,7 +1438,7 @@ class QubesDisposableVm(QubesVm): template_vm = kwargs.pop("template_vm") - super(QubesDisposableVm, self).__init__(dir_path=None, **kwargs) + super(QubesDisposableVm, self).__init__(dir_path="/nonexistent", **kwargs) qid = kwargs["qid"] assert template_vm is not None, "Missing template_vm for DisposableVM!" @@ -1503,7 +1503,8 @@ class QubesAppVm(QubesCowVm): root = xml.etree.ElementTree.Element( "QubesFirwallRules", policy = "allow" if conf["allow"] else "deny", - dns = "allow" if conf["allowDns"] else "deny" + dns = "allow" if conf["allowDns"] else "deny", + icmp = "allow" if conf["allowIcmp"] else "deny" ) for rule in conf["rules"]: @@ -1537,7 +1538,7 @@ class QubesAppVm(QubesCowVm): return True def get_firewall_conf(self): - conf = { "rules": list(), "allow": True, "allowDns": True } + conf = { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True } try: tree = xml.etree.ElementTree.parse(self.firewall_conf) @@ -1545,6 +1546,7 @@ class QubesAppVm(QubesCowVm): conf["allow"] = (root.get("policy") == "allow") conf["allowDns"] = (root.get("dns") == "allow") + conf["allowIcmp"] = (root.get("icmp") == "allow") for element in root: rule = {} diff --git a/dom0/qvm-tools/qvm-run b/dom0/qvm-tools/qvm-run index 6125ad5d..b626d929 100755 --- a/dom0/qvm-tools/qvm-run +++ b/dom0/qvm-tools/qvm-run @@ -29,10 +29,14 @@ import socket import errno import dbus import time +import os +import os.path qubes_guid_path = "/usr/bin/qubes_guid" qubes_clipd_path = "/usr/bin/qclipd" qubes_qfilexchgd_path= "/usr/bin/qfilexchgd" +qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" +qrexec_client_path = "/usr/lib/qubes/qrexec_client" notify_object = None # how long (in sec) to wait for VMs to shutdown @@ -45,6 +49,15 @@ def tray_notify(str, label, timeout = 3000): def tray_notify_error(str, timeout = 3000): notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") +def actually_execute(domid, cmd, options): + args = [qrexec_client_path, "-d", domid, cmd] + if options.localcmd is not None: + args += [ "-l", options.localcmd] + if options.passio and not options.run_on_all_running: + os.execv(qrexec_client_path, args) + exit(1) + args += ["-e"] + subprocess.call(args) def vm_run_cmd(vm, cmd, options): if options.shutdown: @@ -78,6 +91,7 @@ def vm_run_cmd(vm, cmd, options): if options.tray: tray_notify ("Starting the '{0}' VM...".format(vm.name), label=vm.label) xid = vm.start(verbose=options.verbose) + except (IOError, OSError, QubesException) as err: print "ERROR: {0}".format(err) if options.tray: @@ -90,39 +104,37 @@ def vm_run_cmd(vm, cmd, options): subprocess.call(["kdialog", "--error", "Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(vm.name)]) exit (1) - if options.verbose: - print "--> Starting Qubes GUId..." + if os.getenv("DISPLAY") is not None: + if options.verbose: + print "--> Starting Qubes GUId..." - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon, "-l", str(vm.label.index)]) - if (retcode != 0) : - print "ERROR: Cannot start qubes_guid!" - if options.tray: - tray_notify_error ("ERROR: Cannot start qubes_guid!") - exit (1) - else: # VM already running... - guid_is_running = True - xid = vm.get_xid() - s = socket.socket (socket.AF_UNIX) - try: - s.connect ("/var/run/qubes/cmd_socket.{0}".format(xid)) - except (IOError, OSError) as e: - if e.errno in [errno.ENOENT,errno.ECONNREFUSED]: - guid_is_running = False - else: - print "ERROR: unix-connect: {0}".format(e) + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) + if (retcode != 0) : + print "ERROR: Cannot start qubes_guid!" if options.tray: - tray_notify_error ("ERROR: Cannot connect to GUI daemon for this VM!") - exit(1) - if guid_is_running: - s.send (cmd) - s.close() - else: - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon, "-l", str(vm.label.index)]) + tray_notify_error ("ERROR: Cannot start qubes_guid!") + exit (1) + + if options.verbose: + print "--> Starting Qubes rexec daemon..." + + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) + + actually_execute(str(xid), cmd, options); + + else: # VM already running... + xid = vm.get_xid() + if os.getenv("DISPLAY") is not None and not os.path.isfile("/var/run/qubes/guid_running.{0}".format(xid)): + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) if (retcode != 0) : print "ERROR: Cannot start qubes_guid!" if options.tray: tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!") exit (1) + actually_execute(str(xid), cmd, options); def main(): usage = "usage: %prog [options] [] []" @@ -153,6 +165,11 @@ def main(): parser.add_option ("--unpause", action="store_true", dest="unpause", default=False, help="Do 'xm unpause' for the VM(s) (can be combined this with --all and --wait)") + parser.add_option ("--pass_io", action="store_true", dest="passio", default=False, + help="Pass stdin/stdout/stderr from remote program") + + parser.add_option ("--localcmd", action="store", dest="localcmd", default=None, + help="With --pass_io, pass stdin/stdout/stderr to the given program") (options, args) = parser.parse_args () diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start index 197a3c2c..8a3d277e 100755 --- a/dom0/qvm-tools/qvm-start +++ b/dom0/qvm-tools/qvm-start @@ -24,8 +24,10 @@ from qubes.qubes import QubesVmCollection from qubes.qubes import QubesException from optparse import OptionParser import subprocess +import os qubes_guid_path = "/usr/bin/qubes_guid" +qrexec_daemon_path = "/usr/lib/qubes/qrexec_daemon" def main(): usage = "usage: %prog [options] " @@ -33,6 +35,8 @@ def main(): parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) parser.add_option ("--no-guid", action="store_true", dest="noguid", default=False, help="Do not start the GUId") + parser.add_option ("--no-rexec", action="store_true", dest="norexec", default=False, + help="Do not start rexec") parser.add_option ("--console", action="store_true", dest="debug_console", default=False, help="Attach debugging console to the newly started VM") parser.add_option ("--dvm", action="store_true", dest="preparing_dvm", default=False, @@ -60,15 +64,23 @@ def main(): print "ERROR: {0}".format(err) exit (1) - if options.noguid: - exit (0) - if options.verbose: - print "--> Starting Qubes GUId..." + if not options.noguid and os.getenv("DISPLAY") is not None: + if options.verbose: + print "--> Starting Qubes GUId..." - retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) - if (retcode != 0) : - print "ERROR: Cannot start qubes_guid!" - exit (1) + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon, "-l", str(vm.label.index)]) + if (retcode != 0) : + print "ERROR: Cannot start qubes_guid!" + exit (1) + + if not options.norexec: + if options.verbose: + print "--> Starting Qubes rexec..." + + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec_daemon!" + exit (1) main() diff --git a/dom0/restore/block.qubes b/dom0/restore/block.qubes index 435418a2..4aee7f31 100755 --- a/dom0/restore/block.qubes +++ b/dom0/restore/block.qubes @@ -28,7 +28,7 @@ hexnumber() process() { if ! [ "x""$1" = "xfile" ] ; then - exec /etc/xen/scripts/block "$@" + exec /etc/xen/scripts/block $ORIG_ARGS fi while true ; do dev=$(losetup -f --show $2) @@ -48,6 +48,8 @@ XENBUS_PATH="${XENBUS_PATH:?}" if ! [ "$1" = "add" ] || ! [ -f /var/run/qubes/fast_block_attach ] ; then exec /etc/xen/scripts/block "$@" fi + +ORIG_ARGS="$@" vars=$(xenstore-read "$XENBUS_PATH/type" "$XENBUS_PATH/params") process $vars diff --git a/dom0/restore/qfile-daemon b/dom0/restore/qfile-daemon new file mode 100755 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 +# +# 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/dom0/restore/qfile-daemon-dvm b/dom0/restore/qfile-daemon-dvm new file mode 100755 index 00000000..886d858b --- /dev/null +++ b/dom0/restore/qfile-daemon-dvm @@ -0,0 +1,133 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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 dbus +import subprocess +import sys + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from qubes.qubes import QubesDaemonPidfile +from qubes.qmemman_client import QMemmanClient + +current_savefile = '/var/run/qubes/current_savefile' +notify_object = None + +class QfileDaemonDvm: + def __init__(self, name): + self.name = name + + def do_get_dvm(self): + qmemman_client = QMemmanClient() + if not qmemman_client.request_memory(400*1024*1024): + qmemman_client.close() + errmsg = 'Not enough memory to create DVM. ' + errmsg +='Terminate some appVM and retry.' + subprocess.call(['/usr/bin/kdialog', '--sorry', errmsg]) + return None + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + vm = qvm_collection.get_vm_by_name(self.name) + if vm is None: + sys.stderr.write( 'Domain ' + vmname + ' does not exist ?') + qvm_collection.unlock_db() + qmemman_client.close() + return None + retcode = subprocess.call(['/usr/lib/qubes/qubes_restore', + current_savefile, + '-c', vm.label.color, + '-i', vm.label.icon, + '-l', str(vm.label.index)]) + qmemman_client.close() + if retcode != 0: + subprocess.call(['/usr/bin/kdialog', '--sorry', 'DisposableVM creation failed, see qubes_restore.log']) + qvm_collection.unlock_db() + return None + f = open('/var/run/qubes/dispVM_xid', 'r'); + disp_xid = f.readline().rstrip('\n') + disp_name = f.readline().rstrip('\n') + disptempl = f.readline().rstrip('\n') + f.close() + vm_disptempl = qvm_collection.get_vm_by_name(disptempl); + if vm_disptempl is None: + sys.stderr.write( 'Domain ' + disptempl + ' does not exist ?') + qvm_collection.unlock_db() + return None + qvm_collection.add_new_disposablevm(disp_name, vm_disptempl.template_vm, label=vm.label) + qvm_collection.save() + qvm_collection.unlock_db() + + return disp_name + + def dvm_setup_ok(self): + dvmdata_dir = '/var/lib/qubes/dvmdata/' + if not os.path.isfile(current_savefile): + return False + if not os.path.isfile(dvmdata_dir+'default_savefile') or not os.path.isfile(dvmdata_dir+'savefile_root'): + return False + dvm_mtime = os.stat(current_savefile).st_mtime + root_mtime = os.stat(dvmdata_dir+'savefile_root').st_mtime + if dvm_mtime < root_mtime: + return False + return True + + def tray_notify(self, str, timeout = 3000): + notify_object.Notify("Qubes", 0, "red", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + def tray_notify_error(self, str, timeout = 3000): + notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + def get_dvm(self): + if not self.dvm_setup_ok(): + self.tray_notify("Updating DisposableVM savefile, please wait") + if os.system("qvm-create-default-dvm --default-template --default-script >/var/run/qubes/qvm-create-default-dvm.stdout /proc/sys/net/ipv4/ip_forward fi + success echo "" return 0 diff --git a/qrexec/Makefile b/qrexec/Makefile new file mode 100644 index 00000000..6ecd0711 --- /dev/null +++ b/qrexec/Makefile @@ -0,0 +1,14 @@ +CC=gcc +CFLAGS+=-g -Wall -I../vchan -I../common +XENLIBS=-lvchan -lu2mfn -lxenstore -lxenctrl +COMMONIOALL=../common/ioall.o + +all: qrexec_daemon qrexec_agent qrexec_client +qrexec_daemon: qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o buffer.o write_stdin.o + $(CC) -L../vchan -L../u2mfn -g -o qrexec_daemon qrexec_daemon.o unix_server.o $(COMMONIOALL) txrx-vchan.o write_stdin.o buffer.o $(XENLIBS) +qrexec_agent: qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) + $(CC) -L../vchan -L../u2mfn -g -o qrexec_agent qrexec_agent.o exec.o txrx-vchan.o write_stdin.o buffer.o $(COMMONIOALL) $(XENLIBS) +qrexec_client: qrexec_client.o $(COMMONIOALL) exec.o + $(CC) -g -o qrexec_client qrexec_client.o $(COMMONIOALL) exec.o +clean: + rm -f *.o *~ qrexec_daemon qrexec_agent qrexec_client diff --git a/qrexec/buffer.c b/qrexec/buffer.c new file mode 100644 index 00000000..be36a039 --- /dev/null +++ b/qrexec/buffer.c @@ -0,0 +1,93 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include "buffer.h" + +#define BUFFER_LIMIT 50000000 +static int total_mem; +static char *limited_malloc(int len) +{ + char *ret; + total_mem += len; + if (total_mem > BUFFER_LIMIT) { + fprintf(stderr, "attempt to allocate >BUFFER_LIMIT\n"); + exit(1); + } + ret = malloc(len); + if (!ret) { + perror("malloc"); + exit(1); + } + return ret; +} + +static void limited_free(char *ptr, int len) +{ + free(ptr); + total_mem -= len; +} + +void buffer_init(struct buffer *b) +{ + b->buflen = 0; + b->data = NULL; +} + +void buffer_free(struct buffer *b) +{ + if (b->buflen) + limited_free(b->data, b->buflen); + buffer_init(b); +} + +void buffer_append(struct buffer *b, char *data, int len) +{ + int newsize = len + b->buflen; + char *qdata = limited_malloc(len + b->buflen); + memcpy(qdata, b->data, b->buflen); + memcpy(qdata + b->buflen, data, len); + buffer_free(b); + b->buflen = newsize; + b->data = qdata; +} + +void buffer_remove(struct buffer *b, int len) +{ + int newsize = b->buflen - len; + char *qdata = limited_malloc(newsize); + memcpy(qdata, b->data + len, newsize); + buffer_free(b); + b->buflen = newsize; + b->data = qdata; +} + +int buffer_len(struct buffer *b) +{ + return b->buflen; +} + +void *buffer_data(struct buffer *b) +{ + return b->data; +} diff --git a/qrexec/buffer.h b/qrexec/buffer.h new file mode 100644 index 00000000..80b3779d --- /dev/null +++ b/qrexec/buffer.h @@ -0,0 +1,32 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +struct buffer { + char *data; + int buflen; +}; + +void buffer_init(struct buffer *b); +void buffer_free(struct buffer *b); +void buffer_append(struct buffer *b, char *data, int len); +void buffer_remove(struct buffer *b, int len); +int buffer_len(struct buffer *b); +void *buffer_data(struct buffer *b); diff --git a/qrexec/exec.c b/qrexec/exec.c new file mode 100644 index 00000000..a87f5050 --- /dev/null +++ b/qrexec/exec.c @@ -0,0 +1,74 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include + +extern void do_exec(char *); + +void fix_fds(int fdin, int fdout, int fderr) +{ + int i; + for (i = 0; i < 256; i++) + if (i != fdin && i != fdout && i != fderr) + close(i); + dup2(fdin, 0); + dup2(fdout, 1); + dup2(fderr, 2); + close(fdin); + close(fdout); + if (fderr != 2) + close(fderr); +} + +void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, + int *stderr_fd) +{ + int inpipe[2], outpipe[2], errpipe[2]; + + if (pipe(inpipe) || pipe(outpipe) || (stderr_fd && pipe(errpipe))) { + perror("pipe"); + exit(1); + } + switch (*pid = fork()) { + case -1: + perror("fork"); + exit(-1); + case 0: + if (stderr_fd) { + fix_fds(inpipe[0], outpipe[1], errpipe[1]); + } else + fix_fds(inpipe[0], outpipe[1], 2); + + do_exec(cmdline); + exit(-1); + default:; + } + close(inpipe[0]); + close(outpipe[1]); + *stdin_fd = inpipe[1]; + *stdout_fd = outpipe[0]; + if (stderr_fd) { + close(errpipe[1]); + *stderr_fd = errpipe[0]; + } +} diff --git a/qrexec/glue.h b/qrexec/glue.h new file mode 100644 index 00000000..60d697ad --- /dev/null +++ b/qrexec/glue.h @@ -0,0 +1,48 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include + +void do_fork_exec(char *cmdline, int *pid, int *stdin_fd, int *stdout_fd, + int *stderr_fd); +int peer_server_init(int port); +char *peer_client_init(int dom, int port); +void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset); +int read_ready_vchan_ext(); +int read_all_vchan_ext(void *buf, int size); +int write_all_vchan_ext(void *buf, int size); +int buffer_space_vchan_ext(); +void fix_fds(int fdin, int fdout, int fderr); + +int get_server_socket(int domid, char * domname); +int do_accept(int s); + +enum { + WRITE_STDIN_OK = 0x200, + WRITE_STDIN_BUFFERED, + WRITE_STDIN_ERROR +}; + +int flush_client_data(int fd, int clid, struct buffer *buffer); +int write_stdin(int fd, int clid, char *data, int len, + struct buffer *buffer); +void set_nonblock(int fd); +int fork_and_flush_stdin(int fd, struct buffer *buffer); diff --git a/qrexec/qrexec.h b/qrexec/qrexec.h new file mode 100644 index 00000000..ec29ed51 --- /dev/null +++ b/qrexec/qrexec.h @@ -0,0 +1,66 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#define QREXEC_DAEMON_SOCKET_DIR "/var/run/qubes" +#define MAX_FDS 256 +#define MAX_DATA_CHUNK 4096 + +#define REXEC_PORT 512 + +#define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec_agent" + +enum { + MSG_CLIENT_TO_SERVER_EXEC_CMDLINE = 0x100, + MSG_CLIENT_TO_SERVER_JUST_EXEC, + + MSG_SERVER_TO_AGENT_EXEC_CMDLINE, + MSG_SERVER_TO_AGENT_JUST_EXEC, + MSG_SERVER_TO_AGENT_INPUT, + MSG_SERVER_TO_AGENT_CLIENT_END, + + MSG_XOFF, + MSG_XON, + + MSG_AGENT_TO_SERVER_STDOUT, + MSG_AGENT_TO_SERVER_STDERR, + MSG_AGENT_TO_SERVER_EXIT_CODE, + MSG_AGENT_TO_SERVER_TRIGGER_EXEC, + + MSG_SERVER_TO_CLIENT_STDOUT, + MSG_SERVER_TO_CLIENT_STDERR, + MSG_SERVER_TO_CLIENT_EXIT_CODE +}; + +enum { + QREXEC_EXECUTE_FILE_COPY=0x700, + QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM +}; + +struct server_header { + unsigned int type; + unsigned int clid; + unsigned int len; +}; + +struct client_header { + unsigned int type; + unsigned int len; +}; diff --git a/qrexec/qrexec_agent.c b/qrexec/qrexec_agent.c new file mode 100644 index 00000000..24683c71 --- /dev/null +++ b/qrexec/qrexec_agent.c @@ -0,0 +1,539 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +enum fdtype { + FDTYPE_INVALID, + FDTYPE_STDOUT, + FDTYPE_STDERR +}; + +struct _process_fd { + int clid; + int type; + int is_blocked; +}; +struct _client_info { + int stdin_fd; + int stdout_fd; + int stderr_fd; + + int pid; + int is_blocked; + int is_close_after_flush_needed; + struct buffer buffer; +}; + +int max_process_fd = -1; + +/* indexed by file descriptor */ +struct _process_fd process_fd[MAX_FDS]; + +/* indexed by client id, which is descriptor number of a client in daemon */ +struct _client_info client_info[MAX_FDS]; + +int trigger_fd; + +void init() +{ + peer_server_init(REXEC_PORT); + umask(0); + mkfifo(QREXEC_AGENT_TRIGGER_PATH, 0666); + umask(077); + trigger_fd = + open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); +} + +void no_colon_in_cmd() +{ + fprintf(stderr, + "cmdline is supposed to be in user:command form\n"); + exit(1); +} + +void do_exec_directly(char *cmd) +{ + struct passwd *pwd; + char *sep = index(cmd, ':'); + if (!sep) + no_colon_in_cmd(); + *sep = 0; + pwd = getpwnam(cmd); + if (!pwd) { + perror("getpwnam"); + exit(1); + } + setgid(pwd->pw_gid); + initgroups(cmd, pwd->pw_gid); + setuid(pwd->pw_uid); + setenv("HOME", pwd->pw_dir, 1); + setenv("USER", cmd, 1); + execl(sep + 1, sep + 1, NULL); + perror("execl"); + exit(1); +} + +void do_exec(char *cmd) +{ + char *sep = index(cmd, ':'); + if (!sep) + no_colon_in_cmd(); + *sep = 0; + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + if (!strcmp(cmd, "directly")) + do_exec_directly(sep + 1); + execl("/bin/su", "su", "-", cmd, "-c", sep + 1, NULL); + perror("execl"); + exit(1); +} + +void handle_just_exec(int clid, int len) +{ + char buf[len]; + int fdn, pid; + + read_all_vchan_ext(buf, len); + switch (pid = fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + fdn = open("/dev/null", O_RDWR); + fix_fds(fdn, fdn, fdn); + do_exec(buf); + perror("execl"); + exit(1); + default:; + } + fprintf(stderr, "executed (nowait) %s pid %d\n", buf, pid); +} + +void handle_exec(int clid, int len) +{ + char buf[len]; + int pid, stdin_fd, stdout_fd, stderr_fd; + + read_all_vchan_ext(buf, len); + + do_fork_exec(buf, &pid, &stdin_fd, &stdout_fd, &stderr_fd); + + process_fd[stdout_fd].clid = clid; + process_fd[stdout_fd].type = FDTYPE_STDOUT; + process_fd[stdout_fd].is_blocked = 0; + process_fd[stderr_fd].clid = clid; + process_fd[stderr_fd].type = FDTYPE_STDERR; + process_fd[stderr_fd].is_blocked = 0; + + if (stderr_fd > max_process_fd) + max_process_fd = stderr_fd; + if (stdout_fd > max_process_fd) + max_process_fd = stdout_fd; + + set_nonblock(stdin_fd); + + client_info[clid].stdin_fd = stdin_fd; + client_info[clid].stdout_fd = stdout_fd; + client_info[clid].stderr_fd = stderr_fd; + client_info[clid].pid = pid; + client_info[clid].is_blocked = 0; + client_info[clid].is_close_after_flush_needed = 0; + buffer_init(&client_info[clid].buffer); + + fprintf(stderr, "executed %s pid %d\n", buf, pid); + +} + + +void update_max_process_fd() +{ + int i; + for (i = max_process_fd; + process_fd[i].type == FDTYPE_INVALID && i >= 0; i--); + max_process_fd = i; +} + +void send_exit_code(int clid, int status) +{ + struct server_header s_hdr; + s_hdr.type = MSG_AGENT_TO_SERVER_EXIT_CODE; + s_hdr.clid = clid; + s_hdr.len = sizeof status; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + write_all_vchan_ext(&status, sizeof(status)); + fprintf(stderr, "send exit code for clid %d pid %d\n", clid, + client_info[clid].pid); +} + + +// erase process data structures, possibly forced by remote +void remove_process(int clid, int status) +{ + int i; + if (!client_info[clid].pid) + return; + fork_and_flush_stdin(client_info[clid].stdin_fd, &client_info[clid].buffer); +#if 0 +// let's let it die by itself, possibly after it has received buffered stdin + kill(client_info[clid].pid, SIGKILL); +#endif + if (status != -1) + send_exit_code(clid, status); + + + close(client_info[clid].stdin_fd); + client_info[clid].pid = 0; + client_info[clid].stdin_fd = -1; + client_info[clid].is_blocked = 0; + buffer_free(&client_info[clid].buffer); + + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && process_fd[i].clid == clid) { + process_fd[i].type = FDTYPE_INVALID; + process_fd[i].clid = -1; + process_fd[i].is_blocked = 0; + close(i); + } + update_max_process_fd(); +} + +void handle_input(int clid, int len) +{ + char buf[len]; + + read_all_vchan_ext(buf, len); + if (!client_info[clid].pid) + return; + + if (len == 0) { + if (client_info[clid].is_blocked) + client_info[clid].is_close_after_flush_needed = 1; + else { + close(client_info[clid].stdin_fd); + client_info[clid].stdin_fd = -1; + } + return; + } + + switch (write_stdin + (client_info[clid].stdin_fd, clid, buf, len, + &client_info[clid].buffer)) { + case WRITE_STDIN_OK: + break; + case WRITE_STDIN_BUFFERED: + client_info[clid].is_blocked = 1; + break; + case WRITE_STDIN_ERROR: + remove_process(clid, 128); + break; + default: + fprintf(stderr, "unknown write_stdin?\n"); + exit(1); + } + +} + +void set_blocked_outerr(int clid, int val) +{ + process_fd[client_info[clid].stdout_fd].is_blocked = val; + process_fd[client_info[clid].stderr_fd].is_blocked = val; +} + +void handle_server_data() +{ + struct server_header s_hdr; + read_all_vchan_ext(&s_hdr, sizeof s_hdr); + +// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, +// s_hdr.len); + + switch (s_hdr.type) { + case MSG_XON: + set_blocked_outerr(s_hdr.clid, 0); + break; + case MSG_XOFF: + set_blocked_outerr(s_hdr.clid, 1); + break; + case MSG_SERVER_TO_AGENT_EXEC_CMDLINE: + handle_exec(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_JUST_EXEC: + handle_just_exec(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_INPUT: + handle_input(s_hdr.clid, s_hdr.len); + break; + case MSG_SERVER_TO_AGENT_CLIENT_END: + remove_process(s_hdr.clid, -1); + break; + default: + fprintf(stderr, "msg type from daemon is %d ?\n", + s_hdr.type); + exit(1); + } +} + +void handle_process_data(int fd) +{ + struct server_header s_hdr; + char buf[MAX_DATA_CHUNK]; + int ret; + int len; + + len = buffer_space_vchan_ext(); + if (len <= sizeof s_hdr) + return; + + ret = read(fd, buf, len - sizeof s_hdr); + s_hdr.clid = process_fd[fd].clid; + + if (process_fd[fd].type == FDTYPE_STDOUT) + s_hdr.type = MSG_AGENT_TO_SERVER_STDOUT; + else if (process_fd[fd].type == FDTYPE_STDERR) + s_hdr.type = MSG_AGENT_TO_SERVER_STDERR; + else { + fprintf(stderr, "fd=%d, clid=%d, type=%d ?\n", fd, + process_fd[fd].clid, process_fd[fd].type); + exit(1); + } + s_hdr.len = ret; + if (ret >= 0) { + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + write_all_vchan_ext(buf, ret); + } + if (ret == 0) { + process_fd[fd].type = FDTYPE_INVALID; + process_fd[fd].clid = -1; + process_fd[fd].is_blocked = 0; + close(fd); + update_max_process_fd(); + } + if (ret < 0) + remove_process(process_fd[fd].clid, 127); +} + +volatile int child_exited; + +void sigchld_handler(int x) +{ + child_exited = 1; + signal(SIGCHLD, sigchld_handler); +} + +int find_info(int pid) +{ + int i; + for (i = 0; i < MAX_FDS; i++) + if (client_info[i].pid == pid) + return i; + return -1; +} + + +void handle_process_data_all(fd_set * select_fds) +{ + int i; + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && FD_ISSET(i, select_fds)) + handle_process_data(i); +} + + +void flush_out_err(int clid) +{ + fd_set select_set; + int fd_max = -1; + int i; + int ret; + struct timeval tv; + for (;;) { + FD_ZERO(&select_set); + for (i = 0; i <= max_process_fd; i++) { + if (process_fd[i].type != FDTYPE_INVALID + && !process_fd[i].is_blocked + && process_fd[i].clid == clid) { + FD_SET(i, &select_set); + fd_max = i; + } + } + if (fd_max == -1) + return; + tv.tv_sec = 0; + tv.tv_usec = 0; + ret = select(fd_max + 1, &select_set, NULL, NULL, &tv); + if (ret < 0 && errno != EINTR) { + perror("select"); + exit(1); + } + if (!ret) + return; + handle_process_data_all(&select_set); + } +} + +void reap_children() +{ + int status; + int pid; + int clid; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + clid = find_info(pid); + if (clid < 0) + continue; + flush_out_err(clid); + remove_process(clid, status); + } + child_exited = 0; +} + +int fill_fds_for_select(fd_set * rdset, fd_set * wrset) +{ + int max = -1; + int fd, i; + FD_ZERO(rdset); + FD_ZERO(wrset); + + for (i = 0; i <= max_process_fd; i++) + if (process_fd[i].type != FDTYPE_INVALID + && !process_fd[i].is_blocked) { + FD_SET(i, rdset); + max = i; + } + + FD_SET(trigger_fd, rdset); + if (trigger_fd > max) + max = trigger_fd; + + for (i = 0; i < MAX_FDS; i++) + if (client_info[i].pid > 0 && client_info[i].is_blocked) { + fd = client_info[i].stdin_fd; + FD_SET(fd, wrset); + if (fd > max) + max = fd; + } + return max; +} + +void flush_client_data_agent(int clid) +{ + struct _client_info *info = &client_info[clid]; + switch (flush_client_data(info->stdin_fd, clid, &info->buffer)) { + case WRITE_STDIN_OK: + info->is_blocked = 0; + if (info->is_close_after_flush_needed) { + close(info->stdin_fd); + info->stdin_fd = -1; + info->is_close_after_flush_needed = 0; + } + break; + case WRITE_STDIN_ERROR: + remove_process(clid, 128); + break; + case WRITE_STDIN_BUFFERED: + break; + default: + fprintf(stderr, "unknown flush_client_data?\n"); + exit(1); + } +} + +void handle_trigger_io() +{ + struct server_header s_hdr; + char buf[5]; + int ret; + + s_hdr.clid = 0; + s_hdr.len = 0; + if ((ret = read(trigger_fd, buf, 4)) == 4) { + buf[4] = 0; + if (!strcmp(buf, "FCPR")) + s_hdr.clid = QREXEC_EXECUTE_FILE_COPY; + else if (!strcmp(buf, "DVMR")) + s_hdr.clid = QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM; + if (s_hdr.clid) { + s_hdr.type = MSG_AGENT_TO_SERVER_TRIGGER_EXEC; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + } + } +// trigger_fd is nonblock - so no need to reopen +// not really, need to reopen at EOF + if (ret <= 0) { + close(trigger_fd); + trigger_fd = + open(QREXEC_AGENT_TRIGGER_PATH, O_RDONLY | O_NONBLOCK); + } +} + +int main() +{ + fd_set rdset, wrset; + int max; + int i; + + init(); + signal(SIGCHLD, sigchld_handler); + signal(SIGPIPE, SIG_IGN); + + + for (;;) { + max = fill_fds_for_select(&rdset, &wrset); + if (buffer_space_vchan_ext() <= + sizeof(struct server_header)) + FD_ZERO(&rdset); + + wait_for_vchan_or_argfd(max, &rdset, &wrset); + + while (read_ready_vchan_ext()) + handle_server_data(); + + if (FD_ISSET(trigger_fd, &rdset)) + handle_trigger_io(); + + handle_process_data_all(&rdset); + for (i = 0; i <= MAX_FDS; i++) + if (client_info[i].pid > 0 + && client_info[i].is_blocked + && FD_ISSET(client_info[i].stdin_fd, &wrset)) + flush_client_data_agent(i); + + if (child_exited) + reap_children(); + } +} diff --git a/qrexec/qrexec_client.c b/qrexec/qrexec_client.c new file mode 100644 index 00000000..c378a550 --- /dev/null +++ b/qrexec/qrexec_client.c @@ -0,0 +1,255 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +int connect_unix_socket(char *domname) +{ + int s, len; + struct sockaddr_un remote; + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + return -1; + } + + remote.sun_family = AF_UNIX; + snprintf(remote.sun_path, sizeof remote.sun_path, + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname); + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + if (connect(s, (struct sockaddr *) &remote, len) == -1) { + perror("connect"); + exit(1); + } + return s; +} + +void do_exec(char *prog) +{ + execl("/bin/bash", "bash", "-c", prog, NULL); +} + +int local_stdin_fd, local_stdout_fd; + +void do_exit(int code) +{ + int status; +// sever communication lines; wait for child, if any +// so that qrexec-daemon can count (recursively) spawned processes correctly + close(local_stdin_fd); + close(local_stdout_fd); + waitpid(-1, &status, 0); + exit(code); +} + + +void prepare_local_fds(char *cmdline) +{ + int pid; + if (!cmdline) { + local_stdin_fd = 1; + local_stdout_fd = 0; + return; + } + do_fork_exec(cmdline, &pid, &local_stdin_fd, &local_stdout_fd, + NULL); +} + + +void send_cmdline(int s, int type, char *cmdline) +{ + struct client_header hdr; + hdr.type = type; + hdr.len = strlen(cmdline) + 1; + if (!write_all(s, &hdr, sizeof(hdr)) + || !write_all(s, cmdline, hdr.len)) { + perror("write daemon"); + do_exit(1); + } +} + +void handle_input(int s) +{ + char buf[MAX_DATA_CHUNK]; + int ret; + ret = read(local_stdout_fd, buf, sizeof(buf)); + if (ret < 0) { + perror("read"); + do_exit(1); + } + if (ret == 0) { + local_stdout_fd = -1; + shutdown(s, SHUT_WR); + } + if (!write_all(s, buf, ret)) { + perror("write daemon"); + do_exit(1); + } +} + +void handle_daemon_data(int s) +{ + int status; + struct client_header hdr; + char buf[MAX_DATA_CHUNK]; + + if (!read_all(s, &hdr, sizeof hdr)) { + perror("read daemon"); + do_exit(1); + } + if (hdr.len > MAX_DATA_CHUNK) { + fprintf(stderr, "client_header.len=%d\n", hdr.len); + do_exit(1); + } + if (!read_all(s, buf, hdr.len)) { + perror("read daemon"); + do_exit(1); + } + + switch (hdr.type) { + case MSG_SERVER_TO_CLIENT_STDOUT: + if (hdr.len == 0) + close(local_stdin_fd); + else if (!write_all(local_stdin_fd, buf, hdr.len)) { + perror("write local stdout"); + do_exit(1); + } + break; + case MSG_SERVER_TO_CLIENT_STDERR: + write_all(2, buf, hdr.len); + break; + case MSG_SERVER_TO_CLIENT_EXIT_CODE: + status = *(unsigned int *) buf; + if (WIFEXITED(status)) + do_exit(WEXITSTATUS(status)); + else + do_exit(255); + break; + default: + fprintf(stderr, "unknown msg %d\n", hdr.type); + do_exit(1); + } +} + +// perhaps we could save a syscall if we include both sides in both +// rdset and wrset; to be investigated +void handle_daemon_only_until_writable(s) +{ + fd_set rdset, wrset; + + do { + FD_ZERO(&rdset); + FD_ZERO(&wrset); + FD_SET(s, &rdset); + FD_SET(s, &wrset); + + if (select(s + 1, &rdset, &wrset, NULL, NULL) < 0) { + perror("select"); + do_exit(1); + } + if (FD_ISSET(s, &rdset)) + handle_daemon_data(s); + } while (!FD_ISSET(s, &wrset)); +} + +void select_loop(int s) +{ + fd_set select_set; + int max; + for (;;) { + handle_daemon_only_until_writable(s); + FD_ZERO(&select_set); + FD_SET(s, &select_set); + max = s; + if (local_stdout_fd != -1) { + FD_SET(local_stdout_fd, &select_set); + if (s < local_stdout_fd) + max = local_stdout_fd; + } + if (select(max + 1, &select_set, NULL, NULL, NULL) < 0) { + perror("select"); + do_exit(1); + } + if (FD_ISSET(s, &select_set)) + handle_daemon_data(s); + if (local_stdout_fd != -1 + && FD_ISSET(local_stdout_fd, &select_set)) + handle_input(s); + } +} + +void usage(char *name) +{ + fprintf(stderr, + "usage: %s -d domain_num [-l local_prog] -e remote_cmdline\n" + "-e means exit after sending cmd\n", name); + exit(1); +} + +int main(int argc, char **argv) +{ + int opt; + char *domname = NULL; + int s; + int just_exec = 0; + char *local_cmdline = NULL; + while ((opt = getopt(argc, argv, "d:l:e")) != -1) { + switch (opt) { + case 'd': + domname = strdup(optarg); + break; + case 'l': + local_cmdline = strdup(optarg); + break; + case 'e': + just_exec = 1; + break; + default: + usage(argv[0]); + } + } + if (optind >= argc || !domname) + usage(argv[0]); + + s = connect_unix_socket(domname); + setenv("QREXEC_REMOTE_DOMAIN", domname, 1); + prepare_local_fds(local_cmdline); + + if (just_exec) + send_cmdline(s, MSG_CLIENT_TO_SERVER_JUST_EXEC, + argv[optind]); + else { + send_cmdline(s, MSG_CLIENT_TO_SERVER_EXEC_CMDLINE, + argv[optind]); + select_loop(s); + } + return 0; +} diff --git a/qrexec/qrexec_daemon.c b/qrexec/qrexec_daemon.c new file mode 100644 index 00000000..518ba4e7 --- /dev/null +++ b/qrexec/qrexec_daemon.c @@ -0,0 +1,452 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +enum client_flags { + CLIENT_INVALID = 0, + CLIENT_CMDLINE = 1, + CLIENT_DATA = 2, + CLIENT_DONT_READ = 4, + CLIENT_OUTQ_FULL = 8 +}; + +struct _client { + int state; + struct buffer buffer; +}; + +struct _client clients[MAX_FDS]; + +int max_client_fd = -1; +int server_fd; + +void handle_usr1(int x) +{ + exit(0); +} + +void sigchld_handler(int x); + +char *remote_domain_name; + +void init(int xid) +{ + char dbg_log[256]; + int logfd; + + if (xid <= 0) { + fprintf(stderr, "domain id=0?\n"); + exit(1); + } + signal(SIGUSR1, handle_usr1); + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + pause(); + exit(0); + } + close(0); + snprintf(dbg_log, sizeof(dbg_log), + "/var/log/qubes/qrexec.%d.log", xid); + umask(0007); + logfd = open(dbg_log, O_WRONLY | O_CREAT | O_TRUNC, 0640); + umask(0077); + + dup2(logfd, 1); + dup2(logfd, 2); + + chdir("/var/run/qubes"); + if (setsid() < 0) { + perror("setsid()"); + exit(1); + } + + remote_domain_name = peer_client_init(xid, REXEC_PORT); + setuid(getuid()); + server_fd = get_server_socket(xid, remote_domain_name); + signal(SIGPIPE, SIG_IGN); + signal(SIGCHLD, sigchld_handler); + signal(SIGUSR1, SIG_DFL); + kill(getppid(), SIGUSR1); +} + +void handle_new_client() +{ + int fd = do_accept(server_fd); + if (fd >= MAX_FDS) { + fprintf(stderr, "too many clients ?\n"); + exit(1); + } + clients[fd].state = CLIENT_CMDLINE; + buffer_init(&clients[fd].buffer); + if (fd > max_client_fd) + max_client_fd = fd; +} + +int children_count; + +void flush_client(int fd) +{ + int i; + struct server_header s_hdr; + + if (fork_and_flush_stdin(fd, &clients[fd].buffer)) + children_count++; + close(fd); + clients[fd].state = CLIENT_INVALID; + buffer_free(&clients[fd].buffer); + if (max_client_fd == fd) { + for (i = fd; clients[i].state == CLIENT_INVALID && i >= 0; + i--); + max_client_fd = i; + } + s_hdr.type = MSG_SERVER_TO_AGENT_CLIENT_END; + s_hdr.clid = fd; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); +} + +void pass_to_agent(int fd, struct server_header *s_hdr) +{ + int len = s_hdr->len; + char buf[len]; + if (!read_all(fd, buf, len)) { + flush_client(fd); + return; + } + write_all_vchan_ext(s_hdr, sizeof(*s_hdr)); + write_all_vchan_ext(buf, len); +} + +void handle_client_cmdline(int fd) +{ + struct client_header hdr; + struct server_header s_hdr; + if (!read_all(fd, &hdr, sizeof hdr)) { + flush_client(fd); + return; + } + switch (hdr.type) { + case MSG_CLIENT_TO_SERVER_EXEC_CMDLINE: + s_hdr.type = MSG_SERVER_TO_AGENT_EXEC_CMDLINE; + break; + case MSG_CLIENT_TO_SERVER_JUST_EXEC: + s_hdr.type = MSG_SERVER_TO_AGENT_JUST_EXEC; + break; + default: + flush_client(fd); + return; + } + + s_hdr.clid = fd; + s_hdr.len = hdr.len; + pass_to_agent(fd, &s_hdr); + clients[fd].state = CLIENT_DATA; + set_nonblock(fd); + if (hdr.type == MSG_CLIENT_TO_SERVER_JUST_EXEC) + flush_client(fd); + +} + +void handle_client_data(int fd) +{ + struct server_header s_hdr; + char buf[MAX_DATA_CHUNK]; + int len, ret; + + if (clients[fd].state == CLIENT_CMDLINE) { + handle_client_cmdline(fd); + return; + } + len = buffer_space_vchan_ext(); + if (len <= sizeof s_hdr) + return; + ret = read(fd, buf, len - sizeof(s_hdr)); + if (ret < 0) { + perror("read client"); + flush_client(fd); + return; + } + s_hdr.clid = fd; + s_hdr.len = ret; + s_hdr.type = MSG_SERVER_TO_AGENT_INPUT; + + write_all_vchan_ext(&s_hdr, sizeof(s_hdr)); + write_all_vchan_ext(buf, ret); + if (ret == 0) + clients[fd].state |= CLIENT_DONT_READ; +} + +void flush_client_data_daemon(int clid) +{ + switch (flush_client_data(clid, clid, &clients[clid].buffer)) { + case WRITE_STDIN_OK: + clients[clid].state &= ~CLIENT_OUTQ_FULL; + break; + case WRITE_STDIN_ERROR: + flush_client(clid); + break; + case WRITE_STDIN_BUFFERED: + break; + default: + fprintf(stderr, "unknown flush_client_data?\n"); + exit(1); + } +} + +void pass_to_client(int clid, struct client_header *hdr) +{ + int len = hdr->len; + char buf[sizeof(*hdr) + len]; + + *(struct client_header *) buf = *hdr; + read_all_vchan_ext(buf + sizeof(*hdr), len); + + switch (write_stdin + (clid, clid, buf, len + sizeof(*hdr), + &clients[clid].buffer)) { + case WRITE_STDIN_OK: + break; + case WRITE_STDIN_BUFFERED: + clients[clid].state |= CLIENT_OUTQ_FULL; + break; + case WRITE_STDIN_ERROR: + flush_client(clid); + break; + default: + fprintf(stderr, "unknown write_stdin?\n"); + exit(1); + } +} + +int child_exited; + +void sigchld_handler(int x) +{ + child_exited = 1; + signal(SIGCHLD, sigchld_handler); +} + +void reap_children() +{ + int status; + while (waitpid(-1, &status, WNOHANG) > 0) + children_count--; + child_exited = 0; +} + +void wait_for_child() +{ + int status; + waitpid(-1, &status, 0); + children_count--; +} + +#define MAX_CHILDREN 10 +void check_children_count() +{ + if (children_count > MAX_CHILDREN) { + fprintf(stderr, + "max number of children reached, waiting for child exit...\n"); + wait_for_child(); + fprintf(stderr, "now children_count=%d, continuing.\n", + children_count); + } +} + +void handle_trigger_exec(int req) +{ + char *rcmd = NULL, *lcmd = NULL; + int i; + + check_children_count(); + switch (req) { + case QREXEC_EXECUTE_FILE_COPY: + rcmd = "directly:user:/usr/lib/qubes/qfile-agent"; + lcmd = "/usr/lib/qubes/qfile-daemon"; + break; + case QREXEC_EXECUTE_FILE_COPY_FOR_DISPVM: + rcmd = "directly:user:/usr/lib/qubes/qfile-agent-dvm"; + lcmd = "/usr/lib/qubes/qfile-daemon-dvm"; + break; + default: + fprintf(stderr, "got trigger exec no %d\n", req); + exit(1); + } + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + children_count++; + return; + } + for (i = 3; i < 256; i++) + close(i); + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + execl("/usr/lib/qubes/qrexec_client", "qrexec_client", "-d", + remote_domain_name, "-l", lcmd, rcmd, NULL); + perror("execl"); + exit(1); +} + +void handle_agent_data() +{ + struct client_header hdr; + struct server_header s_hdr; + read_all_vchan_ext(&s_hdr, sizeof s_hdr); + +// fprintf(stderr, "got %x %x %x\n", s_hdr.type, s_hdr.clid, +// s_hdr.len); + + if (s_hdr.type == MSG_AGENT_TO_SERVER_TRIGGER_EXEC) { + handle_trigger_exec(s_hdr.clid); + return; + } + + if (s_hdr.clid >= MAX_FDS || s_hdr.clid < 0) { + fprintf(stderr, "from agent: clid=%d\n", s_hdr.clid); + exit(1); + } + + if (s_hdr.type == MSG_XOFF) { + clients[s_hdr.clid].state |= CLIENT_DONT_READ; + return; + } + if (s_hdr.type == MSG_XON) { + clients[s_hdr.clid].state &= ~CLIENT_DONT_READ; + return; + } + + switch (s_hdr.type) { + case MSG_AGENT_TO_SERVER_STDOUT: + hdr.type = MSG_SERVER_TO_CLIENT_STDOUT; + break; + case MSG_AGENT_TO_SERVER_STDERR: + hdr.type = MSG_SERVER_TO_CLIENT_STDERR; + break; + case MSG_AGENT_TO_SERVER_EXIT_CODE: + hdr.type = MSG_SERVER_TO_CLIENT_EXIT_CODE; + break; + default: + fprintf(stderr, "from agent: type=%d\n", s_hdr.type); + exit(1); + } + hdr.len = s_hdr.len; + if (hdr.len > MAX_DATA_CHUNK) { + fprintf(stderr, "agent feeded %d of data bytes?\n", + hdr.len); + exit(1); + } + if (clients[s_hdr.clid].state == CLIENT_INVALID) { + // benefit of doubt - maybe client exited earlier + char buf[MAX_DATA_CHUNK]; + read_all_vchan_ext(buf, s_hdr.len); + return; + } + pass_to_client(s_hdr.clid, &hdr); + if (s_hdr.type == MSG_AGENT_TO_SERVER_EXIT_CODE) + flush_client(s_hdr.clid); +} + +int fill_fds_for_select(fd_set * rdset, fd_set * wrset) +{ + int i; + int max = -1; + FD_ZERO(rdset); + FD_ZERO(wrset); + for (i = 0; i <= max_client_fd; i++) { + if (clients[i].state != CLIENT_INVALID + && !(clients[i].state & CLIENT_DONT_READ)) { + FD_SET(i, rdset); + max = i; + } + if (clients[i].state != CLIENT_INVALID + && clients[i].state & CLIENT_OUTQ_FULL) { + FD_SET(i, wrset); + max = i; + } + } + FD_SET(server_fd, rdset); + if (server_fd > max) + max = server_fd; + return max; +} + +int main(int argc, char **argv) +{ + fd_set rdset, wrset; + int i; + int max; + + if (argc != 2) { + fprintf(stderr, "usage: %s domainid\n", argv[0]); + exit(1); + } + init(atoi(argv[1])); + for (;;) { + max = fill_fds_for_select(&rdset, &wrset); + if (buffer_space_vchan_ext() <= + sizeof(struct server_header)) + FD_ZERO(&rdset); + + wait_for_vchan_or_argfd(max, &rdset, &wrset); + + if (FD_ISSET(server_fd, &rdset)) + handle_new_client(); + + while (read_ready_vchan_ext()) + handle_agent_data(); + + for (i = 0; i <= max_client_fd; i++) + if (clients[i].state != CLIENT_INVALID + && FD_ISSET(i, &rdset)) + handle_client_data(i); + + for (i = 0; i <= max_client_fd; i++) + if (clients[i].state != CLIENT_INVALID + && FD_ISSET(i, &wrset)) + flush_client_data_daemon(i); + if (child_exited) + reap_children(); + + } +} diff --git a/qrexec/txrx-vchan.c b/qrexec/txrx-vchan.c new file mode 100644 index 00000000..2a95180f --- /dev/null +++ b/qrexec/txrx-vchan.c @@ -0,0 +1,207 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include + +static struct libvchan *ctrl; +static int is_server; +int write_all_vchan_ext(void *buf, int size) +{ + int written = 0; + int ret; + + while (written < size) { + ret = + libvchan_write(ctrl, (char *) buf + written, + size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } +// fprintf(stderr, "sent %d bytes\n", size); + return size; +} + + +int read_all_vchan_ext(void *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = + libvchan_read(ctrl, (char *) buf + written, + size - written); + if (ret == 0) { + fprintf(stderr, "EOF\n"); + exit(1); + } + if (ret < 0) { + perror("read"); + exit(1); + } + written += ret; + } +// fprintf(stderr, "read %d bytes\n", size); + return size; +} + +int read_ready_vchan_ext() +{ + return libvchan_data_ready(ctrl); +} + +int buffer_space_vchan_ext() +{ + return libvchan_buffer_space(ctrl); +} + +// if the remote domain is destroyed, we get no notification +// thus, we check for the status periodically + +static int xc_handle = -1; +void slow_check_for_libvchan_is_eof(struct libvchan *ctrl) +{ + struct evtchn_status evst; + evst.port = ctrl->evport; + evst.dom = DOMID_SELF; + if (xc_evtchn_status(xc_handle, &evst)) { + perror("xc_evtchn_status"); + exit(1); + } + if (evst.status != EVTCHNSTAT_interdomain) { + fprintf(stderr, "event channel disconnected\n"); + exit(0); + } +} + + +int wait_for_vchan_or_argfd_once(int max, fd_set * rdset, fd_set * wrset) +{ + int vfd, ret; + struct timeval tv = { 1, 100000 }; + vfd = libvchan_fd_for_select(ctrl); + FD_SET(vfd, rdset); + if (vfd > max) + max = vfd; + max++; + ret = select(max, rdset, wrset, NULL, &tv); + if (ret < 0) { + if (errno != EINTR) { + perror("select"); + exit(1); + } else { + FD_ZERO(rdset); + FD_ZERO(wrset); + fprintf(stderr, "eintr\n"); + return 1; + } + + } + if (libvchan_is_eof(ctrl)) { + fprintf(stderr, "libvchan_is_eof\n"); + exit(0); + } + if (!is_server && ret == 0) + slow_check_for_libvchan_is_eof(ctrl); + if (FD_ISSET(vfd, rdset)) + // the following will never block; we need to do this to + // clear libvchan_fd pending state + libvchan_wait(ctrl); + return ret; +} + +void wait_for_vchan_or_argfd(int max, fd_set * rdset, fd_set * wrset) +{ + fd_set r = *rdset, w = *wrset; + do { + *rdset = r; + *wrset = w; + } + while (wait_for_vchan_or_argfd_once(max, rdset, wrset) == 0); +} + +int peer_server_init(int port) +{ + is_server = 1; + ctrl = libvchan_server_init(port); + if (!ctrl) { + perror("libvchan_server_init"); + exit(1); + } + return 0; +} + +char *peer_client_init(int dom, int port) +{ + struct xs_handle *xs; + char buf[64]; + char *name; + char *dummy; + unsigned int len = 0; + char devbuf[128]; + unsigned int count; + char **vec; + +// double_buffered = 1; // writes to vchan are buffered, nonblocking +// double_buffer_init(); + xs = xs_daemon_open(); + if (!xs) { + perror("xs_daemon_open"); + exit(1); + } + snprintf(buf, sizeof(buf), "/local/domain/%d/name", dom); + name = xs_read(xs, 0, buf, &len); + if (!name) { + perror("xs_read domainname"); + exit(1); + } + snprintf(devbuf, sizeof(devbuf), + "/local/domain/%d/device/vchan/%d/event-channel", dom, + port); + xs_watch(xs, devbuf, devbuf); + do { + vec = xs_read_watch(xs, &count); + if (vec) + free(vec); + len = 0; + dummy = xs_read(xs, 0, devbuf, &len); + } + while (!dummy || !len); // wait for the server to create xenstore entries + free(dummy); + xs_daemon_close(xs); + + // now client init should succeed; "while" is redundant + while (!(ctrl = libvchan_client_init(dom, port))); + + xc_handle = xc_interface_open(); + if (xc_handle < 0) { + perror("xc_interface_open"); + exit(1); + } + return name; +} diff --git a/qrexec/unix_server.c b/qrexec/unix_server.c new file mode 100644 index 00000000..14a61273 --- /dev/null +++ b/qrexec/unix_server.c @@ -0,0 +1,76 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include "qrexec.h" + +int get_server_socket(int domid, char *domname) +{ + struct sockaddr_un sockname; + int s; + char socket_address[40]; + char link_to_socket_name[strlen(domname) + sizeof(socket_address)]; + + snprintf(socket_address, sizeof(socket_address), + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%d", domid); + snprintf(link_to_socket_name, sizeof link_to_socket_name, + QREXEC_DAEMON_SOCKET_DIR "/qrexec.%s", domname); + unlink(socket_address); + unlink(link_to_socket_name); + symlink(socket_address, link_to_socket_name); + + s = socket(AF_UNIX, SOCK_STREAM, 0); + memset(&sockname, 0, sizeof(sockname)); + sockname.sun_family = AF_UNIX; + memcpy(sockname.sun_path, socket_address, strlen(socket_address)); + + if (bind(s, (struct sockaddr *) &sockname, sizeof(sockname)) == -1) { + printf("bind() failed\n"); + close(s); + exit(1); + } +// chmod(sockname.sun_path, 0666); + if (listen(s, 5) == -1) { + perror("listen() failed\n"); + close(s); + exit(1); + } + return s; +} + +int do_accept(int s) +{ + struct sockaddr_un peer; + unsigned int addrlen; + int fd; + addrlen = sizeof(peer); + fd = accept(s, (struct sockaddr *) &peer, &addrlen); + if (fd == -1) { + perror("unix accept"); + exit(1); + } + return fd; +} diff --git a/qrexec/write_stdin.c b/qrexec/write_stdin.c new file mode 100644 index 00000000..f2bbae9e --- /dev/null +++ b/qrexec/write_stdin.c @@ -0,0 +1,131 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include "buffer.h" +#include "glue.h" + +int flush_client_data(int fd, int clid, struct buffer *buffer) +{ + int ret; + int len; + for (;;) { + len = buffer_len(buffer); + if (len > MAX_DATA_CHUNK) + len = MAX_DATA_CHUNK; + ret = write(fd, buffer_data(buffer), len); + if (ret == -1) { + if (errno != EAGAIN) { + return WRITE_STDIN_ERROR; + } else + return WRITE_STDIN_BUFFERED; + } + buffer_remove(buffer, len); + len = buffer_len(buffer); + if (!len) { + struct server_header s_hdr; + s_hdr.type = MSG_XON; + s_hdr.clid = clid; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + return WRITE_STDIN_OK; + } + } + +} + +int write_stdin(int fd, int clid, char *data, int len, + struct buffer *buffer) +{ + int ret; + int written = 0; + + if (buffer_len(buffer)) { + buffer_append(buffer, data, len); + return WRITE_STDIN_BUFFERED; + } + while (written < len) { + ret = write(fd, data + written, len - written); + if (ret == 0) { + perror("write_stdin: write returns 0 ???"); + exit(1); + } + if (ret == -1) { + struct server_header s_hdr; + + if (errno != EAGAIN) + return WRITE_STDIN_ERROR; + + buffer_append(buffer, data + written, + len - written); + + s_hdr.type = MSG_XOFF; + s_hdr.clid = clid; + s_hdr.len = 0; + write_all_vchan_ext(&s_hdr, sizeof s_hdr); + + return WRITE_STDIN_BUFFERED; + } + written += ret; + } + return WRITE_STDIN_OK; + +} + +void set_nonblock(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl | O_NONBLOCK); +} + +void set_block(int fd) +{ + int fl = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); +} + +int fork_and_flush_stdin(int fd, struct buffer *buffer) +{ + int i; + if (!buffer_len(buffer)) + return 0; + switch (fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + break; + default: + return 1; + } + for (i = 0; i < MAX_FDS; i++) + if (i != fd && i != 2) + close(i); + set_block(fd); + write_all(fd, buffer_data(buffer), buffer_len(buffer)); + exit(0); +} diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec index 56c62ba3..82df6ee5 100644 --- a/rpm_spec/core-appvm.spec +++ b/rpm_spec/core-appvm.spec @@ -60,6 +60,9 @@ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes %build make clean all make -C ../common +make -C ../vchan +make -C ../u2mfn +make -C ../qrexec %install @@ -67,9 +70,13 @@ mkdir -p $RPM_BUILD_ROOT/etc/init.d cp qubes_core_appvm $RPM_BUILD_ROOT/etc/init.d/ mkdir -p $RPM_BUILD_ROOT/var/lib/qubes mkdir -p $RPM_BUILD_ROOT/usr/bin -cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm $RPM_BUILD_ROOT/usr/bin +cp qubes_timestamp qvm-copy-to-vm qvm-open-in-dvm qvm-open-in-dvm2 $RPM_BUILD_ROOT/usr/bin +cp qvm-copy-to-vm2 $RPM_BUILD_ROOT/usr/bin 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 qvm-copy-to-vm2.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 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} @@ -86,6 +93,18 @@ cp xorg-preload-apps.conf $RPM_BUILD_ROOT/etc/X11 mkdir -p $RPM_BUILD_ROOT/home_volatile/user chown 500:500 $RPM_BUILD_ROOT/home_volatile/user +install -D ../vchan/libvchan.h $RPM_BUILD_ROOT/usr/include/libvchan.h +install -D ../u2mfn/u2mfnlib.h $RPM_BUILD_ROOT/usr/include/u2mfnlib.h +install -D ../u2mfn/u2mfn-kernel.h $RPM_BUILD_ROOT/usr/include/u2mfn-kernel.h + +install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so +install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so + +mkdir -p $RPM_BUILD_ROOT/var/run/qubes + +%triggerin -- initscripts +cp /var/lib/qubes/serial.conf /etc/init/serial.conf + %post chkconfig --add qubes_core_appvm || echo "WARNING: Cannot add service qubes_core!" @@ -111,14 +130,22 @@ rm -rf $RPM_BUILD_ROOT %defattr(-,root,root,-) /etc/init.d/qubes_core_appvm /usr/bin/qvm-copy-to-vm +/usr/bin/qvm-copy-to-vm2 /usr/lib/qubes/qvm-copy-to-vm.kde +/usr/lib/qubes/qvm-copy-to-vm2.kde %attr(4755,root,root) /usr/bin/qvm-open-in-dvm +/usr/bin/qvm-open-in-dvm2 /usr/lib/qubes/qvm-dvm-transfer /usr/lib/qubes/meminfo-writer +/usr/lib/qubes/dvm_file_editor %{kde_service_dir}/qvm-copy.desktop %{kde_service_dir}/qvm-dvm.desktop %attr(4755,root,root) /usr/lib/qubes/qubes_penctl /usr/lib/qubes/qubes_add_pendrive_script +/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 %dir /mnt/incoming %dir /mnt/outgoing @@ -127,3 +154,20 @@ rm -rf $RPM_BUILD_ROOT %dir /home_volatile %attr(700,user,user) /home_volatile/user /etc/X11/xorg-preload-apps.conf +/usr/include/libvchan.h +%{_libdir}/libvchan.so +%{_libdir}/libu2mfn.so +%dir /var/run/qubes + + +%package devel +Summary: Include files for qubes core libraries +License: GPL v2 only +Group: Development/Sources + +%description devel + +%files devel +/usr/include/libvchan.h +/usr/include/u2mfnlib.h +/usr/include/u2mfn-kernel.h diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index dd1ec6d6..a6e4cd35 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -51,6 +51,9 @@ python -m compileall qvm-core qmemman python -O -m compileall qvm-core qmemman make -C restore make -C ../common +make -C ../qrexec +make -C ../vchan +make -C ../u2mfn %install @@ -90,10 +93,14 @@ cp aux-tools/reset_vm_configs.py $RPM_BUILD_ROOT/usr/lib/qubes cp pendrive_swapper/qubes_pencmd $RPM_BUILD_ROOT/usr/lib/qubes cp qmemman/server.py $RPM_BUILD_ROOT/usr/lib/qubes/qmemman_daemon.py cp ../common/meminfo-writer $RPM_BUILD_ROOT/usr/lib/qubes/ +cp ../qrexec/qrexec_daemon $RPM_BUILD_ROOT/usr/lib/qubes/ +cp ../qrexec/qrexec_client $RPM_BUILD_ROOT/usr/lib/qubes/ 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 @@ -127,6 +134,9 @@ cp pm-utils/02qubes-pause-vms $RPM_BUILD_ROOT/usr/lib64/pm-utils/sleep.d/ mkdir -p $RPM_BUILD_ROOT/var/log/qubes mkdir -p $RPM_BUILD_ROOT/var/run/qubes +install -D ../vchan/libvchan.so $RPM_BUILD_ROOT/%{_libdir}/libvchan.so +install -D ../u2mfn/libu2mfn.so $RPM_BUILD_ROOT/%{_libdir}/libu2mfn.so + %post # Create NetworkManager configuration if we do not have it @@ -284,6 +294,8 @@ fi /usr/lib/qubes/qubes_pencmd /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 @@ -307,6 +319,10 @@ fi /etc/xen/scripts/block-snapshot /etc/xen/scripts/block-origin /etc/xen/scripts/vif-route-qubes +/usr/lib/qubes/qrexec_client +%attr(4750,root,qubes) /usr/lib/qubes/qrexec_daemon %attr(4750,root,qubes) /usr/lib/qubes/xenfreepages %attr(2770,root,qubes) %dir /var/log/qubes %attr(770,root,qubes) %dir /var/run/qubes +%{_libdir}/libvchan.so +%{_libdir}/libu2mfn.so diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec index 97af562d..026ea0fb 100644 --- a/rpm_spec/core-netvm.spec +++ b/rpm_spec/core-netvm.spec @@ -45,6 +45,8 @@ The Qubes core files for installation inside a Qubes NetVM. %pre %build +make -C ../vchan +make -C ../u2mfn %install @@ -89,4 +91,3 @@ rm -rf $RPM_BUILD_ROOT /etc/NetworkManager/dispatcher.d/qubes_nmhook /etc/NetworkManager/dispatcher.d/30-qubes_external_ip /etc/xen/scripts/vif-route-qubes -%dir /var/run/qubes diff --git a/u2mfn/Makefile b/u2mfn/Makefile new file mode 100644 index 00000000..9f08dcce --- /dev/null +++ b/u2mfn/Makefile @@ -0,0 +1,33 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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. +# +# + +CC=gcc +CFLAGS=-g -Wall +all: libu2mfn.so + +libu2mfn.so : u2mfnlib.o + gcc -shared -o libu2mfn.so u2mfnlib.o +u2mfnlib.o: u2mfnlib.c + gcc -fPIC -Wall -g -c u2mfnlib.c +clean: + rm -f *.o *so *~ libu2mfn.so + + diff --git a/u2mfn/u2mfn-kernel.h b/u2mfn/u2mfn-kernel.h new file mode 100644 index 00000000..ee244bc1 --- /dev/null +++ b/u2mfn/u2mfn-kernel.h @@ -0,0 +1,26 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ +#include + +#define U2MFN_MAGIC 0xf5 // See ioctl-number.txt in kernel docs + +#define U2MFN_GET_MFN_FOR_PAGE _IOW (U2MFN_MAGIC, 1, int) +#define U2MFN_GET_LAST_MFN _IO (U2MFN_MAGIC, 2) diff --git a/u2mfn/u2mfnlib.c b/u2mfn/u2mfnlib.c new file mode 100644 index 00000000..998f47e9 --- /dev/null +++ b/u2mfn/u2mfnlib.c @@ -0,0 +1,75 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include "u2mfn-kernel.h" + + +static int u2mfn_fd = -1; + +static int get_fd() +{ + if (u2mfn_fd == -1) { + u2mfn_fd = open("/proc/u2mfn", O_RDWR); + if (u2mfn_fd < 0) + return -1; + } + return 0; +} + +int u2mfn_get_mfn_for_page(long va, int *mfn) +{ + if (get_fd()) + return -1; + *mfn = ioctl(u2mfn_fd, U2MFN_GET_MFN_FOR_PAGE, va); + if (*mfn == -1) + return -1; + + return 0; +} + +int u2mfn_get_last_mfn(int *mfn) +{ + if (get_fd()) + return -1; + + *mfn = ioctl(u2mfn_fd, U2MFN_GET_LAST_MFN, 0); + if (*mfn == -1) + return -1; + + return 0; +} + + + +char *u2mfn_alloc_kpage() +{ + char *ret; + if (get_fd()) + return MAP_FAILED; + ret = + mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, u2mfn_fd, 0); + return ret; +} diff --git a/u2mfn/u2mfnlib.h b/u2mfn/u2mfnlib.h new file mode 100644 index 00000000..e64431af --- /dev/null +++ b/u2mfn/u2mfnlib.h @@ -0,0 +1,24 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +int u2mfn_get_mfn_for_page(long va, int *mfn) ; +int u2mfn_get_last_mfn(int *mfn) ; +char *u2mfn_alloc_kpage(void) ; diff --git a/vchan/Makefile b/vchan/Makefile new file mode 100644 index 00000000..ca7cc724 --- /dev/null +++ b/vchan/Makefile @@ -0,0 +1,39 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# 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. +# +# + +CC=gcc +CFLAGS=-g -Wall -I../u2mfn +all: libvchan.so + +libvchan.so : init.o io.o + gcc -shared -o libvchan.so init.o io.o -L ../u2mfn -lu2mfn +init.o: init.c + gcc -fPIC -Wall -g -c init.c +io.o: io.c + gcc -fPIC -Wall -g -c io.c +node: node.o libvchan.so + gcc -g -o node node.o -L. -lvchan -lxenctrl -lxenstore +node-select: node-select.o libvchan.so + gcc -g -o node-select node-select.o -L. -lvchan -lxenctrl -lxenstore +clean: + rm -f *.o *so *~ client server node node-select + + diff --git a/vchan/init.c b/vchan/init.c new file mode 100644 index 00000000..4a3da4f2 --- /dev/null +++ b/vchan/init.c @@ -0,0 +1,224 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libvchan.h" +#include "../u2mfn/u2mfnlib.h" + +static int ring_init(struct libvchan *ctrl) +{ + int u2mfn = open("/proc/u2mfn", O_RDONLY); + int mfn; + struct vchan_interface *ring; + ring = (struct vchan_interface *) u2mfn_alloc_kpage (); + + if (ring == MAP_FAILED) + return -1; + + ctrl->ring = ring; + if (u2mfn_get_last_mfn (&mfn) < 0) + return -1; + + ctrl->ring_ref = mfn; + close(u2mfn); + ring->cons_in = ring->prod_in = ring->cons_out = ring->prod_out = + 0; + ring->server_closed = ring->client_closed = 0; + ring->debug = 0xaabbccdd; + return 0; +} +/** + creates event channel; + creates "ring-ref" and "event-channel" xenstore entries; + waits for connection to event channel from the peer +*/ +static int server_interface_init(struct libvchan *ctrl, int devno) +{ + int ret = -1; + struct xs_handle *xs; + char buf[64]; + char ref[16]; + int evfd; + evtchn_port_or_error_t port; + xs = xs_domain_open(); + if (!xs) { + return ret; + } + evfd = xc_evtchn_open(); + if (evfd < 0) + goto fail; + ctrl->evfd = evfd; + // the following hardcoded 0 is the peer domain id + port = xc_evtchn_bind_unbound_port(evfd, 0); + if (port < 0) + goto fail2; + ctrl->evport = port; + snprintf(ref, sizeof ref, "%d", ctrl->ring_ref); + snprintf(buf, sizeof buf, "device/vchan/%d/ring-ref", devno); + if (!xs_write(xs, 0, buf, ref, strlen(ref))) + goto fail2; + snprintf(ref, sizeof ref, "%d", ctrl->evport); + snprintf(buf, sizeof buf, "device/vchan/%d/event-channel", devno); + if (!xs_write(xs, 0, buf, ref, strlen(ref))) + goto fail2; + // wait for the peer to arrive + if (xc_evtchn_pending(evfd) == -1) + goto fail2; + xc_evtchn_unmask(ctrl->evfd, ctrl->evport); + snprintf(buf, sizeof buf, "device/vchan/%d", devno); + xs_rm(xs, 0, buf); + + ret = 0; + fail2: + if (ret) + close(evfd); + fail: + xs_daemon_close(xs); + return ret; +} + +#define dir_select(dir1, dir2) \ + ctrl->wr_cons = &ctrl->ring->cons_##dir1; \ + ctrl->wr_prod = &ctrl->ring->prod_##dir1; \ + ctrl->rd_cons = &ctrl->ring->cons_##dir2; \ + ctrl->rd_prod = &ctrl->ring->prod_##dir2; \ + ctrl->wr_ring = ctrl->ring->buf_##dir1; \ + ctrl->rd_ring = ctrl->ring->buf_##dir2; \ + ctrl->wr_ring_size = sizeof(ctrl->ring->buf_##dir1); \ + ctrl->rd_ring_size = sizeof(ctrl->ring->buf_##dir2) + +/** + Run in AppVM (any domain). + Sleeps until the connection is established. + \param devno something like a well-known port. + \returns NULL on failure, handle on success +*/ +struct libvchan *libvchan_server_init(int devno) +{ + struct libvchan *ctrl = + (struct libvchan *) malloc(sizeof(struct libvchan)); + if (!ctrl) + return 0; + if (ring_init(ctrl)) + return 0;; + if (server_interface_init(ctrl, devno)) + return 0; +/* + We want the same code for read/write functions, regardless whether + we are client, or server. Thus, we do not access buf_in nor buf_out + buffers directly. Instead, in *_init functions, the dir_select + macro assigns proper values to wr* and rd* pointers, so that they + point to correct one out of buf_in or buf_out related fields. +*/ + dir_select(in, out); + ctrl->is_server = 1; + return ctrl; +} + +/** + retrieves ring-ref and event-channel numbers from xenstore (if + they don't exist, return error, because nobody seems to listen); + map the ring, connect the event channel +*/ +static int client_interface_init(struct libvchan *ctrl, int domain, int devno) +{ + int ret = -1; + unsigned int len; + struct xs_handle *xs; + int xcfd; + char buf[64]; + char *ref; + int evfd; + int remote_port; + xs = xs_daemon_open(); + if (!xs) { + return ret; + } + snprintf(buf, sizeof buf, + "/local/domain/%d/device/vchan/%d/ring-ref", domain, + devno); + ref = xs_read(xs, 0, buf, &len); + if (!ref) + goto fail; + ctrl->ring_ref = atoi(ref); + if (!ctrl->ring_ref) + goto fail; + free(ref); + snprintf(buf, sizeof buf, + "/local/domain/%d/device/vchan/%d/event-channel", domain, + devno); + ref = xs_read(xs, 0, buf, &len); + if (!ref) + goto fail; + remote_port = atoi(ref); + if (!remote_port) + goto fail; + free(ref); + xcfd = xc_interface_open(); + if (xcfd < 0) + goto fail; + ctrl->ring = (struct vchan_interface *) + xc_map_foreign_range(xcfd, domain, 4096, + PROT_READ | PROT_WRITE, ctrl->ring_ref); + close(xcfd); + if (ctrl->ring == 0 || ctrl->ring == MAP_FAILED) + goto fail; + evfd = xc_evtchn_open(); + if (evfd < 0) + goto fail; + ctrl->evfd = evfd; + ctrl->evport = + xc_evtchn_bind_interdomain(evfd, domain, remote_port); + if (ctrl->evport < 0 || xc_evtchn_notify(evfd, ctrl->evport)) + close(evfd); + else + ret = 0; + fail: + xs_daemon_close(xs); + return ret; +} + +/** + Run on the client side of connection (currently, must be dom0). + \returns NULL on failure (e.g. noone listening), handle on success +*/ +struct libvchan *libvchan_client_init(int domain, int devno) +{ + struct libvchan *ctrl = + (struct libvchan *) malloc(sizeof(struct libvchan)); + if (!ctrl) + return 0; + if (client_interface_init(ctrl, domain, devno)) + return 0; +// See comment in libvchan_server_init + dir_select(out, in); + ctrl->is_server = 0; + return ctrl; +} diff --git a/vchan/io.c b/vchan/io.c new file mode 100644 index 00000000..7b524279 --- /dev/null +++ b/vchan/io.c @@ -0,0 +1,159 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include "libvchan.h" +#include +#include +/** + \return How much data is immediately available for reading +*/ +int libvchan_data_ready(struct libvchan *ctrl) +{ + return *ctrl->rd_prod - *ctrl->rd_cons; +} + +/** + \return How much space is available for writing, without blocking +*/ +int libvchan_buffer_space(struct libvchan *ctrl) +{ + return ctrl->wr_ring_size - (*ctrl->wr_prod - *ctrl->wr_cons); +} + +static int do_notify(struct libvchan *ctrl) +{ + return xc_evtchn_notify(ctrl->evfd, ctrl->evport); +} + +/// returns nonzero if the peer has closed connection +int libvchan_is_eof(struct libvchan *ctrl) +{ + if (ctrl->is_server) { + if (ctrl->ring->client_closed) + return -1; + } else { + if (ctrl->ring->server_closed) { + ctrl->ring->client_closed = 1; + do_notify(ctrl); + return -1; + } + + } + return 0; +} + +/// waits for the peer to do any action +/** + \return -1 return value means peer has closed +*/ +int libvchan_wait(struct libvchan *ctrl) +{ + int ret; + ret = xc_evtchn_pending(ctrl->evfd); + if (ret!=-1 && xc_evtchn_unmask(ctrl->evfd, ctrl->evport)) + return -1; + if (ret!=-1 && libvchan_is_eof(ctrl)) + return -1; + return ret; +} + +/** + may sleep (only if no buffer space available); + may write less data than requested; + returns the amount of data processed, -1 on error or peer close +*/ +int libvchan_write(struct libvchan *ctrl, char *data, int size) +{ + int avail, avail_contig; + int real_idx; + while ((avail = libvchan_buffer_space(ctrl)) == 0) + if (libvchan_wait(ctrl) < 0) + return -1; + if (avail > size) + avail = size; + real_idx = (*ctrl->wr_prod) & (ctrl->wr_ring_size - 1); + avail_contig = ctrl->wr_ring_size - real_idx; + if (avail_contig < avail) + avail = avail_contig; + memcpy(ctrl->wr_ring + real_idx, data, avail); + *ctrl->wr_prod += avail; + if (do_notify(ctrl) < 0) + return -1; + return avail; +} + +/** + may sleep (only if no data is available for reading); + may return less data than requested; + returns the amount of data processed, -1 on error or peer close +*/ +int libvchan_read(struct libvchan *ctrl, char *data, int size) +{ + int avail, avail_contig; + int real_idx; + while ((avail = libvchan_data_ready(ctrl)) == 0) + if (libvchan_wait(ctrl) < 0) + return -1; + if (avail > size) + avail = size; + real_idx = (*ctrl->rd_cons) & (ctrl->rd_ring_size - 1); + avail_contig = ctrl->rd_ring_size - real_idx; + if (avail_contig < avail) + avail = avail_contig; + memcpy(data, ctrl->rd_ring + real_idx, avail); + *ctrl->rd_cons += avail; + if (do_notify(ctrl) < 0) + return -1; + return avail; +} + +/** + Wait fot the writes to finish, then notify the peer of closing + On server side, it waits for the peer to acknowledge +*/ +int libvchan_close(struct libvchan *ctrl) +{ + while (*ctrl->wr_prod != *ctrl->wr_cons) + if (libvchan_wait(ctrl) < 0) + return -1; + if (ctrl->is_server) { + ctrl->ring->server_closed = 1; + do_notify(ctrl); + while (!ctrl->ring->client_closed + && libvchan_wait(ctrl) == 0); + } else { + ctrl->ring->client_closed = 1; + do_notify(ctrl); + } + return 0; +} + +/// The fd to use for select() set +int libvchan_fd_for_select(struct libvchan *ctrl) +{ + return ctrl->evfd; +} + +/// Unmasks event channel; must be called before calling select(), and only then +void libvchan_prepare_to_select(struct libvchan *ctrl) +{ + xc_evtchn_unmask(ctrl->evfd, ctrl->evport); +} diff --git a/vchan/libvchan.h b/vchan/libvchan.h new file mode 100644 index 00000000..652284ba --- /dev/null +++ b/vchan/libvchan.h @@ -0,0 +1,60 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include +typedef uint32_t VCHAN_RING_IDX; + +/// struct vchan_interface is placed in memory shared between domains +struct vchan_interface { + // One buffer for each data direction + char buf_in[1024]; + char buf_out[2048]; + // standard consumer/producer interface, one pair per buffer + VCHAN_RING_IDX cons_in, prod_in, cons_out, prod_out; + uint32_t debug; + int client_closed, server_closed; +}; +/// struct libvchan is a control structure, passed to all library calls +struct libvchan { + struct vchan_interface *ring; + uint32_t ring_ref; + /// descriptor to event channel interface + int evfd; + int evport; + VCHAN_RING_IDX *wr_cons, *wr_prod, *rd_cons, *rd_prod; + char *rd_ring, *wr_ring; + int rd_ring_size, wr_ring_size; + int is_server; +}; + +struct libvchan *libvchan_server_init(int devno); + +struct libvchan *libvchan_client_init(int domain, int devno); + +int libvchan_write(struct libvchan *ctrl, char *data, int size); +int libvchan_read(struct libvchan *ctrl, char *data, int size); +int libvchan_wait(struct libvchan *ctrl); +int libvchan_close(struct libvchan *ctrl); +void libvchan_prepare_to_select(struct libvchan *ctrl); +int libvchan_fd_for_select(struct libvchan *ctrl); +int libvchan_is_eof(struct libvchan *ctrl); +int libvchan_data_ready(struct libvchan *ctrl); +int libvchan_buffer_space(struct libvchan *ctrl); diff --git a/vchan/node-select.c b/vchan/node-select.c new file mode 100644 index 00000000..a4f1cdaf --- /dev/null +++ b/vchan/node-select.c @@ -0,0 +1,133 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include "libvchan.h" +#include +#include +#include +#include +int libvchan_write_all(struct libvchan *ctrl, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = libvchan_write(ctrl, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +int write_all(int fd, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + + +void usage() +{ + fprintf(stderr, "usage:\n\tnode-select server nodeid\n" + "or\n" "\tnode-select client domainid nodeid\n"); + exit(1); +} + +#define BUFSIZE 5000 +char buf[BUFSIZE]; + +/** + Simple libvchan application, both client and server. + Both sides may write and read, both from the libvchan and from + stdin/stdout (just like netcat). More code is required to avoid + deadlock when both sides write, and noone reads. +*/ + +int main(int argc, char **argv) +{ + int ret; + int libvchan_fd; + struct libvchan *ctrl = 0; + if (argc < 3) + usage(); + if (!strcmp(argv[1], "server")) + ctrl = libvchan_server_init(atoi(argv[2])); + else if (!strcmp(argv[1], "client")) + ctrl = libvchan_client_init(atoi(argv[2]), atoi(argv[3])); + else + usage(); + if (!ctrl) { + perror("libvchan_*_init"); + exit(1); + } + + libvchan_fd = libvchan_fd_for_select(ctrl); + for (;;) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(0, &rfds); + FD_SET(libvchan_fd, &rfds); +// libvchan_prepare_to_select(ctrl); + ret = select(libvchan_fd + 1, &rfds, NULL, NULL, NULL); + if (ret < 0) { + perror("select"); + exit(1); + } + if (libvchan_is_eof(ctrl)) + exit(0); + if (FD_ISSET(libvchan_fd, &rfds)) +// we don't care about the result, but we need to do the read to +// clear libvchan_fd pendind state + libvchan_wait(ctrl); + while (libvchan_data_ready(ctrl) > 0) { + ret = libvchan_read(ctrl, buf, BUFSIZE); + if (ret < 0) + exit(0); + write_all(1, buf, ret); + } + if (FD_ISSET(0, &rfds)) { + ret = read(0, buf, BUFSIZE); + if (ret == 0) { + libvchan_close(ctrl); + exit(0); + } + if (ret < 0) { + perror("read 0"); + exit(1); + } +// libvchan_write_all can block; so if both sides write a lot, +// we can deadlock. Need higher level solution; would libvchan_write be ok ? + libvchan_write_all(ctrl, buf, ret); + } + + } +} diff --git a/vchan/node.c b/vchan/node.c new file mode 100644 index 00000000..d739fe7f --- /dev/null +++ b/vchan/node.c @@ -0,0 +1,157 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * 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. + * + */ + +#include "libvchan.h" +#include +#include +#include +#include +#include +int libvchan_write_all(struct libvchan *ctrl, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = libvchan_write(ctrl, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +int write_all(int fd, char *buf, int size) +{ + int written = 0; + int ret; + while (written < size) { + ret = write(fd, buf + written, size - written); + if (ret <= 0) { + perror("write"); + exit(1); + } + written += ret; + } + return size; +} + +void usage() +{ + fprintf(stderr, "usage:\n\tnode server [read|write] nodeid\n" + "or\n" "\tnode client [read|write] domainid nodeid\n"); + exit(1); +} + +#define BUFSIZE 5000 +char buf[BUFSIZE]; +void reader(struct libvchan *ctrl) +{ + int size; + for (;;) { + size = rand() % (BUFSIZE - 1) + 1; + size = libvchan_read(ctrl, buf, size); + fprintf(stderr, "#"); + if (size < 0) { + perror("read vchan"); + libvchan_close(ctrl); + exit(1); + } + if (size == 0) + break; + size = write_all(1, buf, size); + if (size < 0) { + perror("stdout write"); + exit(1); + } + if (size == 0) { + perror("write size=0?\n"); + exit(1); + } + } +} + +void writer(struct libvchan *ctrl) +{ + int size; + for (;;) { + size = rand() % (BUFSIZE - 1) + 1; + size = read(0, buf, size); + if (size < 0) { + perror("read stdin"); + libvchan_close(ctrl); + exit(1); + } + if (size == 0) + break; + size = libvchan_write_all(ctrl, buf, size); + fprintf(stderr, "#"); + if (size < 0) { + perror("vchan write"); + exit(1); + } + if (size == 0) { + perror("write size=0?\n"); + exit(1); + } + } +} + + +/** + Simple libvchan application, both client and server. + One side does writing, the other side does reading; both from + standard input/output fds. +*/ +int main(int argc, char **argv) +{ + int seed = time(0); + struct libvchan *ctrl = 0; + int wr; + if (argc < 4) + usage(); + if (!strcmp(argv[2], "read")) + wr = 0; + else if (!strcmp(argv[2], "write")) + wr = 1; + else + usage(); + if (!strcmp(argv[1], "server")) + ctrl = libvchan_server_init(atoi(argv[3])); + else if (!strcmp(argv[1], "client")) + ctrl = libvchan_client_init(atoi(argv[3]), atoi(argv[4])); + else + usage(); + if (!ctrl) { + perror("libvchan_*_init"); + exit(1); + } + + srand(seed); + fprintf(stderr, "seed=%d\n", seed); + if (wr) + writer(ctrl); + else + reader(ctrl); + libvchan_close(ctrl); + return 0; +}