Merge branch 'spring-merge' of ssh://git.qubes-os.org/var/lib/qubes/git/rafal/core into spring-merge

This commit is contained in:
Marek Marczykowski 2011-03-23 19:45:59 -04:00
commit 7f94cf2709
55 changed files with 4250 additions and 54 deletions

View File

@ -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

View File

@ -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

28
appvm/copy_file.c Normal file
View File

@ -0,0 +1,28 @@
#include <unistd.h>
#include <ioall.h>
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;
}

2
appvm/dvm2.h Normal file
View File

@ -0,0 +1,2 @@
#define DVM_FILENAME_SIZE 256
#define DVM_SPOOL "/home/user/.dvmspool"

73
appvm/dvm_file_editor.c Normal file
View File

@ -0,0 +1,73 @@
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <ioall.h>
#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 </dev/null",
filename);
if (system(cmdbuf))
system
("HOME=/home/user DISPLAY=:0 /usr/bin/kdialog --sorry 'Unable to handle mimetype of the requested file!'");
if (stat(filename, &stat_post)) {
perror("stat post");
exit(1);
}
if (stat_pre.st_mtime != stat_post.st_mtime)
send_file_back(filename);
return 0;
}

18
appvm/filecopy.h Normal file
View File

@ -0,0 +1,18 @@
#define FILECOPY_SPOOL "/home/user/.filecopyspool"
#define FILECOPY_VMNAME_SIZE 32
#define PROGRESS_NOTIFY_DELTA (15*1000*1000)
#define MAX_PATH_LENGTH 16384
#define LEGAL_EOF 31415926
struct file_header {
unsigned int namelen;
unsigned int mode;
unsigned long long filelen;
unsigned int atime;
unsigned int atime_nsec;
unsigned int mtime;
unsigned int mtime_nsec;
};
char * copy_file(int outfd, int infd, long long size);

139
appvm/qfile-agent-dvm.c Normal file
View File

@ -0,0 +1,139 @@
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdlib.h>
#include <ioall.h>
#include <unistd.h>
#include <gui-fatal.h>
#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;
}

215
appvm/qfile-agent.c Normal file
View File

@ -0,0 +1,215 @@
#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include <malloc.h>
#include <stdlib.h>
#include <ioall.h>
#include <unistd.h>
#include <gui-fatal.h>
#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, &current, datasize);
client_flags = get_item(data, &current, datasize);
notify_progress(0, PROGRESS_FLAG_INIT);
send_vmname(vmname);
while ((entry = get_item(data, &current, 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;
}

83
appvm/qfile-unpacker.c Normal file
View File

@ -0,0 +1,83 @@
#define _GNU_SOURCE
#include <ioall.h>
#include <grp.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <sys/fsuid.h>
#include <gui-fatal.h>
#include <errno.h>
#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;
}

46
appvm/qvm-copy-to-vm2 Executable file
View File

@ -0,0 +1,46 @@
#!/bin/sh
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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

48
appvm/qvm-copy-to-vm2.kde Executable file
View File

@ -0,0 +1,48 @@
#!/bin/sh
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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

View File

@ -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

View File

@ -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

40
appvm/qvm-open-in-dvm2 Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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

105
appvm/unpack.c Normal file
View File

@ -0,0 +1,105 @@
#include <errno.h>
#include <ioall.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#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);
}

50
common/gui-fatal.c Normal file
View File

@ -0,0 +1,50 @@
#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <malloc.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
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);
}

2
common/gui-fatal.h Normal file
View File

@ -0,0 +1,2 @@
void gui_fatal(const char *fmt, ...);
void gui_nonfatal(const char *fmt, ...);

97
common/ioall.c Normal file
View File

@ -0,0 +1,97 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
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;
}

3
common/ioall.h Normal file
View File

@ -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);

View File

@ -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 ""

View File

@ -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 = {}

View File

@ -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] [<vm-name>] [<cmd>]"
@ -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 ()

View File

@ -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] <vm-name>"
@ -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()

View File

@ -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

59
dom0/restore/qfile-daemon Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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()

133
dom0/restore/qfile-daemon-dvm Executable file
View File

@ -0,0 +1,133 @@
#!/usr/bin/python2.6
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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 </dev/null" ) != 0:
self.tray_notify_error("DVM savefile creation failed")
return None
return self.do_get_dvm()
def remove_disposable_from_qdb(self, name):
qvm_collection = QubesVmCollection()
qvm_collection.lock_db_for_writing()
qvm_collection.load()
vm = qvm_collection.get_vm_by_name(name)
if vm is None:
qvm_collection.unlock_db()
return False
qvm_collection.pop(vm.qid)
qvm_collection.save()
qvm_collection.unlock_db()
def main():
global notify_object
notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
qfile = QfileDaemonDvm(os.getenv("QREXEC_REMOTE_DOMAIN"))
dispname = qfile.get_dvm()
if dispname is not None:
subprocess.call(['/usr/lib/qubes/qrexec_client', '-d', dispname, 'directly:user:/usr/lib/qubes/dvm_file_editor'])
subprocess.call(['/usr/sbin/xm', 'destroy', dispname])
qfile.remove_disposable_from_qdb(dispname)
main()

View File

@ -21,7 +21,7 @@ if ! [ -d $VMDIR ] ; then
echo $VMDIR does not exist ?
exit 1
fi
if ! qvm-start $1 --no-guid --dvm ; then
if ! qvm-start $1 --no-guid --no-rexec --dvm ; then
exit 1
fi

View File

@ -182,6 +182,29 @@ int xend_connect()
return s;
}
void start_rexec(int domid)
{
int pid, status;
char dstr[40];
snprintf(dstr, sizeof(dstr), "%d", domid);
switch (pid = fork()) {
case -1:
perror("fork");
exit(1);
case 0:
execl("/usr/lib/qubes/qrexec_daemon", "qrexec_daemon",
dstr, NULL);
perror("execl");
exit(1);
default:;
}
if (waitpid(pid, &status, 0) < 0) {
perror("waitpid");
exit(1);
}
}
void start_guid(int domid, int argc, char **argv)
{
int i;
@ -197,6 +220,7 @@ void start_guid(int domid, int argc, char **argv)
execv("/usr/bin/qubes_guid", guid_args);
perror("execv");
}
// modify the savefile. fd = fd to the open savefile,
// buf - already read 1st page of the savefile
// pattern - pattern to search for
@ -452,10 +476,11 @@ int main(int argc, char **argv)
resp = recv_resp(fd);
// printf("%s\n", resp);
fprintf(stderr, "time=%s, creating xenstore entries\n", gettime());
#endif
#endif
setup_xenstore(netvm_id, domid, dispid, name);
fprintf(stderr, "time=%s, starting qubes_guid\n", gettime());
rm_fast_flag();
start_rexec(domid);
start_guid(domid, argc, argv);
return 0;
}

View File

@ -28,7 +28,8 @@ if ! [ -d "/var/lib/qubes/vm-templates/$TEMPLATENAME" ] ; then
exit 1
fi
DVMTMPL="$TEMPLATENAME"-dvm
if ! [ -d "/var/lib/qubes/appvms/$DVMTMPL" ] ; then
DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL"
if ! [ -d "$DVMTMPLDIR" ] ; then
if ! qvm-create -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi
fi
if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \
@ -53,3 +54,7 @@ else
chmod 660 $SHMCOPY
ln -s $SHMCOPY $CURRENT
fi
chgrp qubes "$DVMTMPLDIR" "$DVMTMPLDIR"/*
chmod 660 "$DVMTMPLDIR"/*
chmod 770 "$DVMTMPLDIR"

View File

@ -32,6 +32,7 @@ start()
/usr/lib/qubes/qubes_setup_dnat_to_ns
echo "1" > /proc/sys/net/ipv4/ip_forward
fi
success
echo ""
return 0

14
qrexec/Makefile Normal file
View File

@ -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

93
qrexec/buffer.c Normal file
View File

@ -0,0 +1,93 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

32
qrexec/buffer.h Normal file
View File

@ -0,0 +1,32 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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);

74
qrexec/exec.c Normal file
View File

@ -0,0 +1,74 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
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];
}
}

48
qrexec/glue.h Normal file
View File

@ -0,0 +1,48 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/select.h>
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);

66
qrexec/qrexec.h Normal file
View File

@ -0,0 +1,66 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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;
};

539
qrexec/qrexec_agent.c Normal file
View File

@ -0,0 +1,539 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#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();
}
}

255
qrexec/qrexec_client.c Normal file
View File

@ -0,0 +1,255 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <ioall.h>
#include <sys/wait.h>
#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;
}

452
qrexec/qrexec_daemon.c Normal file
View File

@ -0,0 +1,452 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ioall.h>
#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();
}
}

207
qrexec/txrx-vchan.c Normal file
View File

@ -0,0 +1,207 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <libvchan.h>
#include <xs.h>
#include <xenctrl.h>
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;
}

76
qrexec/unix_server.c Normal file
View File

@ -0,0 +1,76 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#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;
}

131
qrexec/write_stdin.c Normal file
View File

@ -0,0 +1,131 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ioall.h>
#include <stdlib.h>
#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);
}

View File

@ -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

View File

@ -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

View File

@ -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

33
u2mfn/Makefile Normal file
View File

@ -0,0 +1,33 @@
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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

26
u2mfn/u2mfn-kernel.h Normal file
View File

@ -0,0 +1,26 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <linux/ioctl.h>
#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)

75
u2mfn/u2mfnlib.c Normal file
View File

@ -0,0 +1,75 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#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;
}

24
u2mfn/u2mfnlib.h Normal file
View File

@ -0,0 +1,24 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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) ;

39
vchan/Makefile Normal file
View File

@ -0,0 +1,39 @@
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
#
# 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

224
vchan/init.c Normal file
View File

@ -0,0 +1,224 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <malloc.h>
#include <xs.h>
#include <string.h>
#include <xenctrl.h>
#include <unistd.h>
#include <stdlib.h>
#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;
}

159
vchan/io.c Normal file
View File

@ -0,0 +1,159 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <xenctrl.h>
#include <string.h>
/**
\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);
}

60
vchan/libvchan.h Normal file
View File

@ -0,0 +1,60 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdint.h>
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);

133
vchan/node-select.c Normal file
View File

@ -0,0 +1,133 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
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);
}
}
}

157
vchan/node.c Normal file
View File

@ -0,0 +1,157 @@
/*
* The Qubes OS Project, http://www.qubes-os.org
*
* Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
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;
}