commit a17989470afc0613847cd7597ea4f74827a2739e Author: Joanna Rutkowska Date: Mon Apr 5 20:58:57 2010 +0200 Initial public commit. (c) 2010 Invisible Things Lab Authors: ========= Joanna Rutkowska Rafal Wojtczuk diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4d22dcb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +rpm/ +*.pyc diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..fe70076f --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +RPMS_DIR=rpm/ +help: + @echo "make rpms -- generate binary rpm packages" + @echo "make update_repo -- copy newly generated rpms to qubes yum repo" + +rpms: + rpmbuild --define "_rpmdir $(RPMS_DIR)" -bb rpm_spec/core-appvm.spec + rpmbuild --define "_rpmdir $(RPMS_DIR)" -bb rpm_spec/core-netvm.spec + rpmbuild --define "_rpmdir $(RPMS_DIR)" -bb rpm_spec/core-dom0.spec + rpmbuild --define "_rpmdir $(RPMS_DIR)" -bb rpm_spec/dom0-cleanup.spec + rpm --addsign $(RPMS_DIR)/x86_64/*.rpm + +update_repo: + ln -f $(RPMS_DIR)/x86_64/*.rpm ../yum/rpm/ + (if [ -d $(RPMS_DIR)/i686 ] ; then ln -f $(RPMS_DIR)/i686/*.rpm ../yum/rpm/; fi) + +clean: + (cd appvm && make clean) diff --git a/TODO b/TODO new file mode 100644 index 00000000..b9b4b429 --- /dev/null +++ b/TODO @@ -0,0 +1,16 @@ +* file exchange -- handle correctly files that have spaces in name +-- qvm-copy-to-vm* do not copy files in the top directory has spaces in the name + +* qvm-update-appmenus +-- let the user install appmenus for (potential) new apps after template update +-- BUT: potential problem of Dom0 needing to mount the template's fs +-- but: perhaps we should trust the template and its update process? + +* netvm: prevent inter-VM networking +-- do not allow one AppVM to send any packets to other AppVMs that use the same netvm + +* qvm-prefs: allow to grow/shrink AppVM's private.img? + +* Dom0 udev scripts: do not load network drivers at all! + +* Dom0: detect when running without VT-d enabled and display a warning diff --git a/appvm/Makefile b/appvm/Makefile new file mode 100644 index 00000000..ef88f081 --- /dev/null +++ b/appvm/Makefile @@ -0,0 +1,9 @@ +CC=gcc +CFLAGS=-Wall +all: qubes_penctl qubes_add_pendrive_script +qubes_penctl: qubes_penctl.o + $(CC) -o qubes_penctl qubes_penctl.o -lxenstore +qubes_add_pendrive_script: qubes_add_pendrive_script.o + $(CC) -o qubes_add_pendrive_script qubes_add_pendrive_script.o +clean: + rm -f qubes_penctl qubes_add_pendrive_script *.o *~ diff --git a/appvm/fstab b/appvm/fstab new file mode 100644 index 00000000..604dbb8f --- /dev/null +++ b/appvm/fstab @@ -0,0 +1,17 @@ + +# +# /etc/fstab +# Created by anaconda on Thu Dec 3 11:26:49 2009 +# +# Accessible filesystems, by reference, are maintained under '/dev/disk' +# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info +# +/dev/mapper/dmroot / ext4 defaults,noatime 1 1 +/dev/mapper/dmswap swap swap defaults 0 0 +/dev/xvdb /rw ext4 defaults 0 0 +tmpfs /dev/shm tmpfs defaults 0 0 +devpts /dev/pts devpts gid=5,mode=620 0 0 +sysfs /sys sysfs defaults 0 0 +proc /proc proc defaults 0 0 +/dev/xvdg /mnt/outgoing vfat noauto,user,rw 0 0 +/dev/xvdh /mnt/incoming vfat noauto,user,rw 0 0 diff --git a/appvm/iptables b/appvm/iptables new file mode 100644 index 00000000..77ad30c2 --- /dev/null +++ b/appvm/iptables @@ -0,0 +1,12 @@ +# Firewall configuration written by system-config-firewall +# Manual customization of this file is not recommended. +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +-A INPUT -p icmp -j ACCEPT +-A INPUT -i lo -j ACCEPT +-A INPUT -j REJECT --reject-with icmp-host-prohibited +-A FORWARD -j REJECT --reject-with icmp-host-prohibited +COMMIT diff --git a/appvm/qubes.rules b/appvm/qubes.rules new file mode 100644 index 00000000..e3fc50e4 --- /dev/null +++ b/appvm/qubes.rules @@ -0,0 +1 @@ +SUBSYSTEM=="block", KERNEL=="xvdh", ACTION=="add", RUN+="/usr/bin/qubes_add_pendrive_script" diff --git a/appvm/qubes_add_pendrive_script.c b/appvm/qubes_add_pendrive_script.c new file mode 100644 index 00000000..8c0fb917 --- /dev/null +++ b/appvm/qubes_add_pendrive_script.c @@ -0,0 +1,87 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +int parse_events(char *buf, int len) +{ + int i = 0; + while (i < len) { + struct inotify_event *ev = (struct inotify_event *)(buf + i); + if ((ev->mask & IN_UNMOUNT) || (ev->mask & IN_IGNORED)) + return 1; + i += sizeof(struct inotify_event) + ev->len; + } + return 0; +} + +#define BUFLEN 1024 +void wait_for_umount(char *name) +{ + char buf[BUFLEN]; + int fd = inotify_init(); + int len; + int ret = inotify_add_watch(fd, name, IN_ATTRIB); + if (ret < 0) { + perror("inotify_add_watch"); + return; + } + for (;;) { + len = read(fd, buf, BUFLEN - 1); + if (len <= 0) { + perror("read inotify"); + return; + } + if (parse_events(buf, len)) + return; + } +} + +void background() +{ + int i, fd; + for (i = 0; i < 256; i++) + close(i); + fd = open("/dev/null", O_RDWR); + for (i = 0; i <= 2; i++) + dup2(fd, i); + switch (fork()) { + case -1: + exit(1); + case 0: break; + default: + exit(0); + } +} + + +#define MOUNTDIR "/mnt/incoming" +int main() +{ + background(); + if (!system("su - user -c 'mount " MOUNTDIR "'")) + wait_for_umount(MOUNTDIR "/."); + system("xenstore-write device/qpen umount"); + return 0; +} diff --git a/appvm/qubes_core b/appvm/qubes_core new file mode 100755 index 00000000..dfedb9fc --- /dev/null +++ b/appvm/qubes_core @@ -0,0 +1,87 @@ +#!/bin/sh +# +# chkconfig: 345 90 90 +# description: Executes Qubes core scripts at VM boot +# +# Source function library. +. /etc/rc.d/init.d/functions + +start() +{ + echo -n $"Executing Qubes Core scripts:" + + if ! [ -d /rw/home ] ; then + echo + echo "--> Virgin boot of the VM: Linking /home to /rw/home" + mv /home /home.orig + mkdir -p /rw/config + mkdir -p /rw/home + ln -s /rw/home/ /home +# chcon --reference /home.orig /rw/home + cp -a /home.orig/user /home + touch /rw/config/rc.local + rm -fr /home.orig + touch /var/lib/qubes/first_boot_completed + else + mv /home /home.tmpl + ln -s /rw/home/ /home + fi + + + if ! [ -x /usr/bin/xenstore-read ] ; then + echo "ERROR: /usr/bin/xenstore-read not found!" + exit 1 + fi + + name=$(/usr/bin/xenstore-read name) + hostname $name + + vmtype=$(/usr/bin/xenstore-read qubes_vm_type) + + if [ $vmtype == 'NetVM' ] ; then + # Setup gateway for all the VMs this netVM is serviceing... + brctl addbr br0 + gateway=$(/usr/bin/xenstore-read qubes_netvm_gateway) + netmask=$(/usr/bin/xenstore-read qubes_netvm_netmask) + network=$(/usr/bin/xenstore-read qubes_netvm_network) + ifconfig br0 $gateway netmask $netmask up + echo "1" > /proc/sys/net/ipv4/ip_forward + dnsmasq + iptables -t nat -A POSTROUTING -s $network/$netmask -j MASQUERADE + else + ip=$(/usr/bin/xenstore-read qubes_ip) + netmask=$(/usr/bin/xenstore-read qubes_netmask) + gateway=$(/usr/bin/xenstore-read qubes_gateway) + if [ x$ip != x ]; then + /sbin/ifconfig eth0 $ip netmask $netmask up + /sbin/route add default gw $gateway + echo "nameserver $gateway" > /etc/resolv.conf + fi + fi + + + [ -x /rw/config/rc.local ] && /rw/config/rc.local + success + echo "" + return 0 +} + +stop() +{ + return 0 +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + *) + echo $"Usage: $0 {start|stop}" + exit 3 + ;; +esac + +exit $RETVAL diff --git a/appvm/qubes_penctl.c b/appvm/qubes_penctl.c new file mode 100644 index 00000000..ab373f02 --- /dev/null +++ b/appvm/qubes_penctl.c @@ -0,0 +1,71 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2010 Rafal Wojtczuk + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +int check_name(unsigned char *s) +{ + int c; + for (; *s; s++) { + c = *s; + if (c >= 'a' && c <= 'z') + continue; + if (c >= 'A' && c <= 'Z') + continue; + if (c == '_' || c == '-') + continue; + return 0; + } + return 1; +} + +int main(int argc, char **argv) +{ + char buf[256] = "new"; + struct xs_handle *xs; + xs = xs_domain_open(); + setuid(getuid()); + if (!xs) { + perror("xs_domain_open"); + exit(1); + } + if (argc < 2) { + fprintf(stderr, "usage: %s new\n" + "%s send vmname\n", argv[0], argv[0]); + exit(1); + } + if (argc > 2) { + if (!check_name((unsigned char*)argv[2])) { + fprintf(stderr, "invalid vmname %s\n", argv[2]); + exit(1); + } + snprintf(buf, sizeof(buf), "send %s", argv[2]); + } + if (!xs_write(xs, 0, "device/qpen", buf, strlen(buf))) { + perror("xs_write"); + exit(1); + } + xs_daemon_close(xs); + return 0; +} diff --git a/appvm/qvm-copy-to-vm b/appvm/qvm-copy-to-vm new file mode 100755 index 00000000..28d94283 --- /dev/null +++ b/appvm/qvm-copy-to-vm @@ -0,0 +1,40 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +if [ $# -lt 2 ] ; then + echo usage: $0 'vmname file [file]*' + exit 1 +fi +qubes_penctl new || exit 1 +echo -n Waiting for the Qubes virtual pendrive +while ! [ -e /dev/xvdg ] ; do + echo -n . + sleep 1 +done +echo " received" +mount /mnt/outgoing +VMNAME=$1 +shift +cp -v -a $* /mnt/outgoing +#sometimes Dolphin lags a bit +umount /mnt/outgoing || (sleep 1; umount /mnt/outgoing) || exit 1 +qubes_penctl send $VMNAME || exit 1 diff --git a/appvm/qvm-copy-to-vm.kde b/appvm/qvm-copy-to-vm.kde new file mode 100755 index 00000000..0278db1d --- /dev/null +++ b/appvm/qvm-copy-to-vm.kde @@ -0,0 +1,42 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +FILES="$*" +VM=$(kdialog -inputbox "Enter the VM name to send $FILE to:") +if [ X$VM = X ] ; then exit 0 ; fi +DEST=/mnt/outgoing +SIZE=$(du -c $FILES | tail -1 | cut -f 1) +REF=$(kdialog --progressbar "Copy progress") +qdbus $REF org.freedesktop.DBus.Properties.Set "" maximum $SIZE +FLAG=$(mktemp) +(qvm-copy-to-vm $VM $FILES ; rm $FLAG) & +while ! grep -q $DEST /proc/mounts && [ -f $FLAG ] ; do + sleep 0.1 +done +while grep -q $DEST /proc/mounts ; do + CURRSIZE=$(du -c $DEST | tail -1 | cut -f 1) + qdbus $REF org.freedesktop.DBus.Properties.Set "" value $CURRSIZE + sleep 1 +done +qdbus $REF close + + diff --git a/appvm/qvm-copy.desktop b/appvm/qvm-copy.desktop new file mode 100644 index 00000000..6d7ebc66 --- /dev/null +++ b/appvm/qvm-copy.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Actions=QvmCopy; +Type=Service +X-KDE-ServiceTypes=KonqPopupMenu/Plugin,inode/directory,all/allfiles + +[Desktop Action QvmCopy] +Exec=/usr/bin/qvm-copy-to-vm.kde %U +Icon=kget +Name=Send To VM + diff --git a/dom0/aux-tools/check_and_remove_appmenu.sh b/dom0/aux-tools/check_and_remove_appmenu.sh new file mode 100755 index 00000000..b93b14a6 --- /dev/null +++ b/dom0/aux-tools/check_and_remove_appmenu.sh @@ -0,0 +1,14 @@ +#!/bin/sh +if grep -q X-Qubes-VmName $1 ; then + exit 0 +fi + +if grep -q "Categories=.*\(System\|Settings\)" $1 ; then + #echo "Leaving file: $1" + exit 0 +fi +BACKUP_DIR="/var/lib/qubes/backup/removed-apps/" +mkdir -p $BACKUP_DIR +#echo "Moving file: $1 to $BACKUP_DIR +mv $1 $BACKUP_DIR + diff --git a/dom0/aux-tools/convert_apptemplate2vm.sh b/dom0/aux-tools/convert_apptemplate2vm.sh new file mode 100755 index 00000000..b5ab1467 --- /dev/null +++ b/dom0/aux-tools/convert_apptemplate2vm.sh @@ -0,0 +1,13 @@ +#!/bin/sh +SRC=$1 +DSTDIR=$2 +VMNAME=$3 +VMDIR=$4 + +DST=$DSTDIR/$VMNAME-$(basename $SRC) + +sed -e "s/%VMNAME%/$VMNAME/" \ + -e "s %VMDIR% $VMDIR " \ + <$SRC >$DST + + diff --git a/dom0/aux-tools/convert_dirtemplate2vm.sh b/dom0/aux-tools/convert_dirtemplate2vm.sh new file mode 100755 index 00000000..9d1972b6 --- /dev/null +++ b/dom0/aux-tools/convert_dirtemplate2vm.sh @@ -0,0 +1,11 @@ +#!/bin/sh +SRC=$1 +DST=$2 +VMNAME=$3 +VMDIR=$4 + +sed -e "s/%VMNAME%/$VMNAME/" \ + -e "s %VMDIR% $VMDIR " \ + <$SRC >$DST + + diff --git a/dom0/aux-tools/create_apps_for_appvm.sh b/dom0/aux-tools/create_apps_for_appvm.sh new file mode 100755 index 00000000..0bf762bb --- /dev/null +++ b/dom0/aux-tools/create_apps_for_appvm.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +SRCDIR=$1 +VMNAME=$2 +VMDIR=/var/lib/qubes/appvms/$VMNAME +APPSDIR=$VMDIR/apps + +if [ $# != 2 ]; then + echo "usage: $0 " + exit +fi +mkdir -p $APPSDIR + +echo "--> Converting Appmenu Templates..." +find $SRCDIR -name "*.desktop" -exec /usr/lib/qubes/convert_apptemplate2vm.sh {} $APPSDIR $VMNAME $VMDIR \; + +/usr/lib/qubes/convert_dirtemplate2vm.sh $SRCDIR/qubes-vm.directory.template $APPSDIR/$VMNAME-vm.directory $VMNAME $VMDIR + +echo "--> Adding Apps to the Menu..." +xdg-desktop-menu install $APPSDIR/*.directory $APPSDIR/*.desktop + diff --git a/dom0/aux-tools/patch_appvm_initramfs.sh b/dom0/aux-tools/patch_appvm_initramfs.sh new file mode 100755 index 00000000..6cc62ebf --- /dev/null +++ b/dom0/aux-tools/patch_appvm_initramfs.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +# +# This script can be used to patch the initramfs of the Qubes AppVM +# It inserts an additional script that is responsible for setting up +# COW-based root fs and VM private fs +# + +INITRAMFS=$1 +INITRAMFS_QUBES=$2 +QUBES_COW_SETUP_FILE=$3 + + +TMP_DIR=`mktemp -d /tmp/qubes-initramfs-patching-XXXXXXX` + +if [ $# != 3 ] ; then + echo "usage: $0 " + exit 0 +fi + +if [ x$INITRAMFS = x ] ; then + echo "INITRAMFS missing!" + exit 1 +fi + +if [ x$INITRAMFS_QUBES = x ] ; then + echo "INITRAMFS_QUBES missing!" + exit 1 +fi + +if [ x$QUBES_COW_SETUP_FILE = x ] ; then + echo "$QUBES_COW_SETUP_FILE missing!" + exit 1 +fi + + +ID=$(id -ur) + +if [ $ID != 0 ] ; then + echo "This script should be run as root user. Apparently the initramfs files must have root.root owener..." + exit 1 +fi + +mkdir $TMP_DIR/initramfs.qubes || exit 1 + +cp $INITRAMFS $TMP_DIR/initramfs.cpio.gz + +pushd $TMP_DIR/initramfs.qubes + +gunzip < ../initramfs.cpio.gz | cpio -i --quiet || exit 1 + +cp $QUBES_COW_SETUP_FILE pre-trigger/90_qubes_cow_setup.sh || exit 1 + +find ./ | cpio -H newc -o --quiet > $TMP_DIR/initramfs.qubes.cpio || exit 1 + +popd + +gzip $TMP_DIR/initramfs.qubes.cpio || exit 1 + +mv $TMP_DIR/initramfs.qubes.cpio.gz $INITRAMFS_QUBES || exit 1 + +rm -fr $TMP_DIR || exit 1 diff --git a/dom0/aux-tools/remove_appvm_appmenus.sh b/dom0/aux-tools/remove_appvm_appmenus.sh new file mode 100755 index 00000000..b163f51b --- /dev/null +++ b/dom0/aux-tools/remove_appvm_appmenus.sh @@ -0,0 +1,12 @@ +#!/bin/sh +VMNAME=$1 +VMDIR=/var/lib/qubes/appvms/$VMNAME +APPSDIR=$VMDIR/apps + +if [ $# != 1 ]; then + echo "usage: $0 " + exit +fi + +xdg-desktop-menu uninstall $APPSDIR/*.directory $APPSDIR/*.desktop + diff --git a/dom0/aux-tools/remove_dom0_appmenus.sh b/dom0/aux-tools/remove_dom0_appmenus.sh new file mode 100755 index 00000000..49811f11 --- /dev/null +++ b/dom0/aux-tools/remove_dom0_appmenus.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "--> Removing unnecessary Dom0 Appmenus..." +find /usr/share/applications -name *.desktop -exec /usr/lib/qubes/check_and_remove_appmenu.sh {} \; + +xdg-desktop-menu forceupdate diff --git a/dom0/aux-tools/unbind_all_network_devices b/dom0/aux-tools/unbind_all_network_devices new file mode 100755 index 00000000..4d6a4a44 --- /dev/null +++ b/dom0/aux-tools/unbind_all_network_devices @@ -0,0 +1,74 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from optparse import OptionParser +import subprocess +import shutil +import re + + +def find_net_devices(): + p = subprocess.Popen (["lspci", "-mm", "-n"], stdout=subprocess.PIPE) + result = p.communicate() + retcode = p.returncode + if (retcode != 0): + print "ERROR when executing lspci!" + raise IOError + + net_devices = set() + rx_netdev = re.compile (r"^([0-9][0-9]:[0-9][0-9].[0-9]) \"02") + for dev in str(result[0]).splitlines(): + match = rx_netdev.match (dev) + if match is not None: + dev_bdf = match.group(1) + assert dev_bdf is not None + net_devices.add (dev_bdf) + + return net_devices + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + parser.add_option ("-v", "--verbose", dest="verbose", action="store_true", default=False) + (options, args) = parser.parse_args () + + if options.verbose: + print "Loading Xen PCI Backend..." + retcode = subprocess.call (["/sbin/modprobe", "xen-pciback"]) + if retcode != 0: + print "ERROR: Cannot load xen-pciback module!" + exit(1) + + if options.verbose: + print "Unbinding the following net devices:" + + net_devices = find_net_devices() + + for dev in net_devices: + if options.verbose: + print "--> {0}".format(dev) + retcode = subprocess.call (["/usr/lib/qubes/unbind_pci_device.sh", dev]) + if (retcode != 0): + print "WARNING: Could not unbind device {0}".format(dev) + + +main() diff --git a/dom0/aux-tools/unbind_pci_device.sh b/dom0/aux-tools/unbind_pci_device.sh new file mode 100755 index 00000000..c54ee2f2 --- /dev/null +++ b/dom0/aux-tools/unbind_pci_device.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +BDF=$1 +if [ x$BDF = x ] ; then + echo "usage: $0 " + exit 0 +fi +BDF=0000:$BDF +#echo -n "Binding device $BDF to xen-pciback..." +if [ -e /sys/bus/pci/devices/$BDF/driver/unbind ] ; then + echo -n $BDF > /sys/bus/pci/devices/$BDF/driver/unbind || exit 1 +fi +echo -n $BDF > /sys/bus/pci/drivers/pciback/new_slot || exit 1 +echo -n $BDF > /sys/bus/pci/drivers/pciback/bind || exit 1 +#echo ok diff --git a/dom0/clipboard_notifier/qclipd b/dom0/clipboard_notifier/qclipd new file mode 100755 index 00000000..abd7612a --- /dev/null +++ b/dom0/clipboard_notifier/qclipd @@ -0,0 +1,85 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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 daemon +from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent +import dbus +from qubes.qubes import QubesDaemonPidfile + +qubes_clipboard_info_file = "/var/run/qubes/qubes_clipboard.bin.source" + +def watch_qubes_clipboard(): + + def tray_notify(str, timeout = 3000): + notify_object.Notify("Qubes", 0, "dialog-information", "", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") + wm = WatchManager() + mask = EventsCodes.OP_FLAGS.get('IN_CLOSE_WRITE') + + if not os.path.exists (qubes_clipboard_info_file): + file = open (qubes_clipboard_info_file, 'w') + file.close() + + + class ClipboardWatcher(ProcessEvent): + def process_IN_CLOSE_WRITE (self, event): + src_info_file = open (qubes_clipboard_info_file, 'r') + src_vmname = src_info_file.readline().strip('\n') + if src_vmname == "": + tray_notify ("Qubes Clipboard has been copied to the VM and wiped.\n"\ + "Trigger a paste operation (e.g. Ctrl-v) to insert it into an application." ) + else: + print src_vmname + tray_notify ("Qubes Clipboard fetched from VM: '{0}'\n"\ + "Press Ctrl-Shift-v to copy this clipboard onto dest VM's clipboard.".format(src_vmname)) + src_info_file.close() + + + notifier = Notifier(wm, ClipboardWatcher()) + wdd = wm.add_watch(qubes_clipboard_info_file, mask) + + while True: + notifier.process_events() + if notifier.check_events(): + notifier.read_events() + + +def main(): + + lock = QubesDaemonPidfile ("qclipd") + if lock.pidfile_exists(): + if lock.pidfile_is_stale(): + lock.remove_pidfile() + print "Removed stale pidfile (has the previous daemon instance crashed?)." + else: + exit (0) + + context = daemon.DaemonContext( + working_directory = "/var/run/qubes", + pidfile = lock) + + with context: + watch_qubes_clipboard() + +main() diff --git a/dom0/icons/black.png b/dom0/icons/black.png new file mode 100644 index 00000000..9095231f Binary files /dev/null and b/dom0/icons/black.png differ diff --git a/dom0/icons/blue.png b/dom0/icons/blue.png new file mode 100644 index 00000000..6a194105 Binary files /dev/null and b/dom0/icons/blue.png differ diff --git a/dom0/icons/credits-crystal-icons b/dom0/icons/credits-crystal-icons new file mode 100644 index 00000000..92a54bb2 --- /dev/null +++ b/dom0/icons/credits-crystal-icons @@ -0,0 +1,10 @@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +This copyright and license notice covers the images in this directory. +************************************************************************ + +TITLE: Crystal Project Icons +AUTHOR: Everaldo Coelho +SITE: http://www.everaldo.com +CONTACT: everaldo@everaldo.com + +Copyright (c) 2006-2007 Everaldo Coelho. diff --git a/dom0/icons/credits-padlock-icons b/dom0/icons/credits-padlock-icons new file mode 100644 index 00000000..9098c418 --- /dev/null +++ b/dom0/icons/credits-padlock-icons @@ -0,0 +1 @@ +Color padlock images downloaded from www.openclipart.org diff --git a/dom0/icons/gray.png b/dom0/icons/gray.png new file mode 100644 index 00000000..3a979eb4 Binary files /dev/null and b/dom0/icons/gray.png differ diff --git a/dom0/icons/green.png b/dom0/icons/green.png new file mode 100644 index 00000000..fb0f5491 Binary files /dev/null and b/dom0/icons/green.png differ diff --git a/dom0/icons/netvm.png b/dom0/icons/netvm.png new file mode 100644 index 00000000..bafb6c68 Binary files /dev/null and b/dom0/icons/netvm.png differ diff --git a/dom0/icons/orange.png b/dom0/icons/orange.png new file mode 100644 index 00000000..1ec4231d Binary files /dev/null and b/dom0/icons/orange.png differ diff --git a/dom0/icons/purple.png b/dom0/icons/purple.png new file mode 100644 index 00000000..0076bfae Binary files /dev/null and b/dom0/icons/purple.png differ diff --git a/dom0/icons/red.png b/dom0/icons/red.png new file mode 100644 index 00000000..240f7fc0 Binary files /dev/null and b/dom0/icons/red.png differ diff --git a/dom0/icons/template.png b/dom0/icons/template.png new file mode 100644 index 00000000..996640c5 Binary files /dev/null and b/dom0/icons/template.png differ diff --git a/dom0/icons/yellow.png b/dom0/icons/yellow.png new file mode 100644 index 00000000..878cc60b Binary files /dev/null and b/dom0/icons/yellow.png differ diff --git a/dom0/init.d/qubes_core b/dom0/init.d/qubes_core new file mode 100755 index 00000000..e0945572 --- /dev/null +++ b/dom0/init.d/qubes_core @@ -0,0 +1,63 @@ +#!/bin/sh +# +# chkconfig: 2345 99 00 +# description: Executes Qubes core scripts at Dom0 boot +# +### BEGIN INIT INFO +# Provides: qubes-core +# Required-Start: xend +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# Default-Enabled: yes +# Short-Description: Start/stop qubes-core services +# Description: Starts and stops the qubes-core serives +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + + + +start() +{ + echo -n $"Executing Qubes Core scripts:" + chgrp qubes /etc/xen + chmod 710 /etc/xen + chgrp qubes /var/run/xend + chmod 710 /var/run/xend + chgrp qubes /var/run/xend/xen-api.sock /var/run/xend/xmlrpc.sock + chmod 660 /var/run/xend/xen-api.sock /var/run/xend/xmlrpc.sock + chgrp qubes /var/run/xenstored/* + chmod 660 /var/run/xenstored/* + xm sched-credit -d 0 -w 65535 + cp /var/lib/qubes/qubes.xml /var/lib/qubes/backup/qubes-$(date +%F-%T).xml + touch /var/lock/subsys/qubes_core + success + echo + +} + +stop() +{ + echo -n $"Shutting down all Qubes VMs:" + NETVM=$(qvm-get-default-netvm) + qvm-run -q --shutdown --all --wait --exclude $NETVM + rm -f /var/lock/subsys/qubes_core + success + echo +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + *) + echo $"Usage: $0 {start|stop}" + exit 3 + ;; +esac + +exit $RETVAL diff --git a/dom0/init.d/qubes_netvm b/dom0/init.d/qubes_netvm new file mode 100755 index 00000000..84ab0e10 --- /dev/null +++ b/dom0/init.d/qubes_netvm @@ -0,0 +1,102 @@ +#!/bin/sh +# +# chkconfig: 2345 99 00 +# description: Starts/stops Qubes default netvm +# +### BEGIN INIT INFO +# Provides: qubes-networking +# Required-Start: qubes-core +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# Default-Enabled: yes +# Short-Description: Start/stop qubes networking +# Description: Starts and stops the qubes networking +### END INIT INFO + + +# +# Source function library. +. /etc/rc.d/init.d/functions + +NETVM=$(qvm-get-default-netvm) + +start() +{ + if [ x$NETVM = x ] ; then + + echo WARNING: Qubes NetVM not configured! + echo -n $"Doing nothing:" + + elif [ $NETVM = "dom0" ] ; then + + echo -n $"Setting up net backend in Dom0:" + /etc/init.d/NetworkManager start + brctl addbr br0 || exit 1 + ifconfig br0 10.0.0.1 netmask 255.255.0.0 up || exit 1 + echo "1" > /proc/sys/net/ipv4/ip_forward || exit 1 + /usr/sbin/dnsmasq --listen-address 10.0.0.1 --bind-interfaces || exit 1 + iptables -t nat -A POSTROUTING -s 10.0.0.0/16 '!' -d 10.0.0.0/16 -j MASQUERADE || exit 1 + iptables -I INPUT 1 -i br0 -s 10.0.0.0/16 -j ACCEPT || exit 1 + iptables -I FORWARD 1 -i br0 -s 10.0.0.0/16 -j ACCEPT || exit 1 + iptables -I FORWARD 1 -o br0 -d 10.0.0.0/16 -m state --state ESTABLISHED,RELATED -j ACCEPT || exit 1 + + else + + echo -n $"Starting default NetVM:" + /usr/lib/qubes/unbind_all_network_devices || exit 1 + qvm-start -q --no-guid $NETVM || exit 1 + + fi + touch /var/lock/subsys/qubes_netvm + success + echo + return 0 +} + +stop() +{ + if [ x$NETVM = x ] ; then + + echo WARNING: Qubes NetVM not configured! + echo -n $"Doing nothing:" + + elif [ $NETVM = "dom0" ] ; then + + echo -n $"Stopping Qubes networking in Dom0:" + iptables -t nat -D POSTROUTING -s 10.0.0.0/16 '!' -d 10.0.0.0/16 -j MASQUERADE + iptables -D INPUT -i br0 -s 10.0.0.0/16 -j ACCEPT || exit 1 + iptables -D FORWARD -i br0 -s 10.0.0.0/16 -j ACCEPT || exit 1 + iptables -D FORWARD -o br0 -d 10.0.0.0/16 -m state --state ESTABLISHED,RELATED -j ACCEPT || exit 1 + + + killall dnsmasq + ifconfig br0 down + brctl delbr br0 + + else + + echo -n $"Stopping default NetVM:" + qvm-run -q --shutdown --wait $NETVM + + fi + rm -f /var/lock/subsys/qubes_netvm + success + echo + + return 0 +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + *) + echo $"Usage: $0 {start|stop}" + exit 3 + ;; +esac + +exit $RETVAL diff --git a/dom0/pendrive_swapper/qfilexchgd b/dom0/pendrive_swapper/qfilexchgd new file mode 100755 index 00000000..f74e6951 --- /dev/null +++ b/dom0/pendrive_swapper/qfilexchgd @@ -0,0 +1,223 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +import xen.lowlevel.xs +import os +import sys +import subprocess +import daemon +import time +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from qubes.qubes import QubesDaemonPidfile + +filename_seq = 50 +pen_cmd = '/usr/lib/qubes/qubes_pencmd' + +def get_next_filename_seq(): + global filename_seq + filename_seq = filename_seq + 1 + return str(filename_seq) + +def logproc(msg): + f = file('/var/log/qubes/qfileexchgd', 'a') + f.write(msg+'\n') + f.close() + +def get_req_node(domain_id): + return '/local/domain/'+domain_id+'/device/qpen' + +def get_name_node(domain_id): + return '/local/domain/'+domain_id+'/name' + +def only_in_first_list(l1, l2): + ret=[] + for i in l1: + if not i in l2: + ret.append(i) + return ret + + +class WatchType: + def __init__(self, fn, param): + self.fn = fn + self.param = param + +class DomainState: + def __init__(self, domain, dict): + self.rcv_state = 'idle' + self.send_state = 'idle' + self.domain_id = domain + self.domdict = dict + self.send_seq = None + self.rcv_seq = None + self.waiting_sender = None + + def handle_request(self, request): + req_ok = False + if request is None: + return + tmp = request.split() + rq = tmp[0] + if len(tmp) > 1: + arg = tmp[1] + else: + arg = None + if rq == 'new' and self.send_state == 'idle': + self.send_seq = get_next_filename_seq() + retcode = subprocess.call([pen_cmd, 'new', self.domain_id, self.send_seq]) + logproc( 'Give domain ' + self.domain_id + ' a clean pendrive, retcode= ' + str(retcode)) + if retcode == 0: + self.send_state = 'has_clean_pendrive' + req_ok = True + if rq == 'send' and self.send_state == 'has_clean_pendrive' and arg is not None: + logproc( 'send from ' + self.domain_id + ' to ' + arg) + if self.handle_transfer(arg): + self.send_state = 'idle' + req_ok = True; + if rq == 'umount' and self.rcv_state == 'has_loaded_pendrive': + retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq]) + if retcode == 0: + self.rcv_state = 'idle' + self.rcv_seq = None + logproc( 'set state of ' + self.domain_id + ' loaded->idle retcode=' + str(retcode)) + req_ok = True + if rq == 'umount' and self.rcv_state == 'waits_to_umount': + req_ok = True + retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq]) + if retcode != 0: + return + assert(self.waiting_sender != None) + self.rcv_state = 'idle' + self.rcv_seq = None + tmp = self.waiting_sender + self.waiting_sender = None + if tmp.send_state == 'has_clean_pendrive': + if tmp.handle_transfer(self.name): + tmp.send_state = 'idle' + + if not req_ok: + logproc( 'request ' + request + ' not served due to nonmatching state') + + def ask_to_umount(self, vmname): + q = 'VM ' + vmname + ' has already an incoming pendrive, and thus ' + q+= 'cannot accept another one. If you intend to unmount its current ' + q+= 'pendrive and retry this transfer, press Yes. ' + q+= 'Otherwise press No to fail this transfer.' + retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'Some additional action required']) + if retcode == 0: + return True + else: + return False + + def handle_transfer(self, vmname): + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None: + logproc( 'Domain ' + vmname + ' does not exist ?') + return False + if not vm.is_running(): + logproc( 'Domain ' + vmname + ' is not running ?') + return False + target=self.domdict[str(vm.get_xid())] + if target.rcv_state != 'idle': + if self.ask_to_umount(vmname): + target.rcv_state='waits_to_umount' + target.waiting_sender=self + logproc( 'target domain ' + target.domain_id + ' is not idle, now ' + target.rcv_state) + return False + retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', 'Do you authorize pendrive transfer from ' + self.name + ' to ' + vmname + '?' , '--title', 'Security confirmation']) + logproc('handle_transfer: kdialog retcode=' + str(retcode)) + if retcode != 0: + return False + target.rcv_state='has_loaded_pendrive' + retcode = subprocess.call([pen_cmd, 'send', self.domain_id, target.domain_id, self.send_seq]) + target.rcv_seq = self.send_seq + self.send_seq = None + logproc( 'set state of ' + target.domain_id + ' to has_loaded_pendrive, retcode=' + str(retcode)) + return True + + +class XS_Watcher: + def __init__(self): + self.handle = xen.lowlevel.xs.xs() + self.handle.watch('/local/domain', WatchType(XS_Watcher.dom_list_change, None)) + self.domdict = {} + + def dom_list_change(self, param): + curr = self.handle.ls('', '/local/domain') + if curr == None: + return + for i in only_in_first_list(curr, self.domdict.keys()): + newdom = DomainState(i, self.domdict) + newdom.watch_token = WatchType(XS_Watcher.request, newdom) + newdom.watch_name = WatchType(XS_Watcher.namechange, newdom) + self.domdict[i] = newdom + self.handle.watch(get_req_node(i), newdom.watch_token) + self.handle.watch(get_name_node(i), newdom.watch_name) + newdom.name = '' + logproc( 'added domain ' + i) + for i in only_in_first_list(self.domdict.keys(), curr): + self.handle.unwatch(get_req_node(i), self.domdict[i].watch_token) + self.handle.unwatch(get_name_node(i), self.domdict[i].watch_name) + self.domdict.pop(i) + logproc( 'removed domain ' + i) + + def request(self, domain_param): + ret = self.handle.read('', get_req_node(domain_param.domain_id)) + domain_param.handle_request(ret) + + def namechange(self, domain_param): + ret = self.handle.read('', get_name_node(domain_param.domain_id)) + if ret!= '' and ret!=None: + domain_param.name = ret + logproc( 'Name for domain xid ' + domain_param.domain_id + ' is ' + ret ) + + def watch_loop(self): + sys.stderr = file('/var/log/qubes/qfileexchgd.errors', 'a') + while True: + result = self.handle.read_watch() + token = result[1] + token.fn(self, token.param) + +def main(): + + lock = QubesDaemonPidfile ("qfileexchgd") + if lock.pidfile_exists(): + if lock.pidfile_is_stale(): + lock.remove_pidfile() + print "Removed stale pidfile (has the previous daemon instance crashed?)." + else: + exit (0) + + + context = daemon.DaemonContext( + working_directory = "/var/run/qubes", + pidfile = lock) + with context: + XS_Watcher().watch_loop() + +main() diff --git a/dom0/pendrive_swapper/qubes_pencmd b/dom0/pendrive_swapper/qubes_pencmd new file mode 100755 index 00000000..9f1db8fb --- /dev/null +++ b/dom0/pendrive_swapper/qubes_pencmd @@ -0,0 +1,74 @@ +#!/bin/sh +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +SHARE_DIR=/var/run/qubes +function do_new() +{ + FILE=$SHARE_DIR/pendrive.$2 + truncate -s 1G $FILE || exit 1 + vmname=$(xenstore-read /local/domain/$1/name) + mkfs.vfat -n "$vmname" $FILE || exit 1 + xm block-attach $1 file:/$FILE /dev/xvdg w || exit 1 +} + +function do_umount() +{ + xm block-detach $1 /dev/xvdh || exit 1 + rm $SHARE_DIR/pendrive.$2 +} + +function do_send() +{ + FILE=$SHARE_DIR/pendrive.$3 + vmname=$(xenstore-read /local/domain/$1/name) + xenstore-write /local/domain/$2/qubes_blocksrc $vmname + xm block-detach $1 /dev/xvdg || exit 1 + xm block-attach $2 file:/$FILE /dev/xvdh w || exit 1 +} + +if [ $# -lt 1 ] ; then + echo args missing ? + exit 1 +fi +if [ "$1" = "new" ] ; then + if ! [ $# = 3 ] ; then + echo new requires 2 more args + exit 1 + fi + do_new "$2" "$3" +elif [ "$1" = "umount" ] ; then + if ! [ $# = 3 ] ; then + echo umount requires 2 more args + exit 1 + fi + do_umount "$2" "$3" +elif [ "$1" = "send" ] ; then + if ! [ $# = 4 ] ; then + echo send requires 3 more args + exit 1 + fi + do_send "$2" "$3" "$4" +else + echo bad cmd + exit 1 +fi + diff --git a/dom0/qvm-core/__init__.py b/dom0/qvm-core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py new file mode 100755 index 00000000..5ba8cbf4 --- /dev/null +++ b/dom0/qvm-core/qubes.py @@ -0,0 +1,1515 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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 sys +import os +import os.path +import subprocess +import xml.etree.ElementTree +import xml.parsers.expat +import fcntl +import re +import shutil + +# Do not use XenAPI or create/read any VM files +# This is for testing only! +dry_run = False +#dry_run = True + + +if not dry_run: + # Xen API + import xmlrpclib + from xen.xm import XenAPI + from xen.xend import sxp + + +if dry_run: + qubes_base_dir = "." +else: + qubes_base_dir = "/var/lib/qubes" + +qubes_appvms_dir = qubes_base_dir + "/appvms" +qubes_templates_dir = qubes_base_dir + "/vm-templates" +qubes_servicevms_dir = qubes_base_dir + "/servicevms" +qubes_store_filename = qubes_base_dir + "/qubes.xml" + +qubes_max_qid = 254*254 +qubes_max_netid = 254 +vm_default_netmask = "255.255.0.0" + +default_root_img = "root.img" +default_rootcow_img = "root-cow.img" +default_swapcow_img = "swap-cow.img" +default_private_img = "private.img" +default_appvms_conf_file = "appvm-template.conf" +default_templatevm_conf_template = "templatevm.conf" # needed for TemplateVM cloning +default_appmenus_templates_subdir = "apps.templates" +default_kernels_subdir = "kernels" + +# do not allow to start a new AppVM if Dom0 mem was to be less than this +dom0_min_memory = 700*1024*1024 + +# We need this global reference, as each instance of QubesVM +# must be able to ask Dom0 VM about how much memory it currently has... +dom0_vm = None + +qubes_appmenu_create_cmd = "/usr/lib/qubes/create_apps_for_appvm.sh" +qubes_appmenu_remove_cmd = "/usr/lib/qubes/remove_appvm_appmenus.sh" + +# TODO: we should detect the actual size of the AppVM's swap partition +# rather than using this ugly hardcoded value, which was choosen here +# as "should be good for everyone" +swap_cow_sz = 1024*1024*1024 + +VM_TEMPLATE = 'TempleteVM' +VM_APPVM = 'AppVM' +VM_NETVM = 'NetVM' + +def get_xend_session_old_api(): + from xen.xend import XendClient + from xen.util.xmlrpcclient import ServerProxy + xend_server = ServerProxy(XendClient.uri) + return xend_server + +def get_xend_session_new_api(): + xend_socket_uri = "httpu:///var/run/xend/xen-api.sock" + session = XenAPI.Session (xend_socket_uri) + session.login_with_password ("", "") + return session + + +class QubesException (Exception) : pass + +class QubesVmLabel(object): + def __init__(self, name, color = None, icon = None): + self.name = name + self.color = color if color is not None else name + self.icon = icon if icon is not None else name + self.icon_path = "/usr/share/qubes/icons/" + self.icon + ".png" + +# Globally defined lables +QubesVmLabels = { + "yellow" : QubesVmLabel ("yellow"), + "orange" : QubesVmLabel ("orange"), + "red" : QubesVmLabel ("red"), + "purple" : QubesVmLabel ("purple"), + "green" : QubesVmLabel ("green", color="0x5fa05e"), + "blue" : QubesVmLabel ("blue"), + "gray" : QubesVmLabel ("gray"), + "black" : QubesVmLabel ("black"), +} + +default_appvm_label = QubesVmLabels["red"] +default_template_label = QubesVmLabels["gray"] +default_servicevm_label = QubesVmLabels["red"] + +class QubesVm(object): + """ + A representation of one Qubes VM + Only persistent information are stored here, while all the runtime + information, e.g. Xen dom id, etc, are to be retrieved via Xen API + Note that qid is not the same as Xen's domid! + """ + + def __init__(self, qid, name, type, + dir_path, conf_file = None, + uses_default_netvm = True, + netvm_vm = None, + installed_by_rpm = False, + updateable = False, + label = None): + + + assert qid < qubes_max_qid, "VM id out of bounds!" + self.__qid = qid + self.name = name + + dir_path = dir_path + self.dir_path = dir_path + conf_file = conf_file + if self.dir_path is not None: + if (conf_file is None): + self.conf_file = dir_path + "/" + name + ".conf" + else: + if os.path.isabs(conf_file): + self.conf_file = conf_file + else: + self.conf_file = dir_path + "/" + conf_file + + self.__type = type + self.uses_default_netvm = uses_default_netvm + self.netvm_vm = netvm_vm + + # We use it in remove from disk to avoid removing rpm files (for templates and netvms) + self.installed_by_rpm = installed_by_rpm + + self.updateable = updateable + self.label = label if label is not None else QubesVmLabels["red"] + self.icon_path = self.dir_path + "/icon.png" + + @property + def qid(self): + return self.__qid + + @property + def type(self): + return self.__type + + @property + def ip(self): + if self.netvm_vm is not None: + return self.netvm_vm.get_ip_for_vm(self.qid) + else: + return None + + @property + def netmask(self): + if self.netvm_vm is not None: + return self.netvm_vm.netmask + else: + return None + + @property + def gateway(self): + if self.netvm_vm is not None: + return self.netvm_vm.gateway + else: + return None + + def is_updateable(self): + return self.updateable + + def is_networked(self): + if self.is_netvm(): + return True + + if self.netvm_vm is not None: + return True + else: + return False + + + def set_nonupdateable(self): + if not self.is_updateable(): + return + + assert not self.is_running() + # We can always downgrade a VM to non-updateable... + self.updateable = False + + def is_templete(self): + if self.type == VM_TEMPLATE: + return True + else: + return False + + def is_appvm(self): + if self.type == VM_APPVM: + return True + else: + return False + + def is_netvm(self): + if self.type == VM_NETVM: + return True + else: + return False + + def add_to_xen_storage(self): + if dry_run: + return + + retcode = subprocess.call (["/usr/sbin/xm", "new", "-q", self.conf_file]) + if retcode != 0: + raise OSError ("Cannot add VM '{0}' to Xen Store!".format(self.name)) + + return True + + def remove_from_xen_storage(self): + if dry_run: + return + + retcode = subprocess.call (["/usr/sbin/xm", "delete", self.name]) + if retcode != 0: + raise OSError ("Cannot remove VM '{0}' from Xen Store!".format(self.name)) + + self.in_xen_storage = False + + def update_xen_storage(self): + self.remove_from_xen_storage() + self.add_to_xen_storage() + + def get_xid(self): + if dry_run: + return 666 + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + xid = int (session.xenapi.VM.get_domid (uuid)) + return xid + + def get_mem(self): + if dry_run: + return 666 + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + metrics = session.xenapi.VM.get_metrics(uuid) + mem = int (session.xenapi.VM_metrics.get_memory_actual (metrics)) + return mem + + def get_mem_static_max(self): + if dry_run: + return 666 + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + mem = int(session.xenapi.VM.get_memory_static_max(uuid)) + return mem + + + def get_cpu_total_load(self): + if dry_run: + import random + return random.random() * 100 + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + metrics = session.xenapi.VM.get_metrics(uuid) + cpus_util = session.xenapi.VM_metrics.get_VCPUs_utilisation (metrics) + if len (cpus_util) == 0: + return 0 + + cpu_total_load = 0.0 + for cpu in cpus_util: + cpu_total_load += cpus_util[cpu] + cpu_total_load /= len(cpus_util) + p = 100*cpu_total_load + if p > 100: + p = 100 + return p + + def get_power_state(self): + if dry_run: + return "NA" + + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + if len (uuids) == 0: + return "NA" + uuid = uuids[0] + power_state = session.xenapi.VM.get_power_state (uuid) + return power_state + + def is_running(self): + if self.get_power_state() == "Running": + return True + else: + return False + + + def get_disk_usage(self, file_or_dir): + if not os.path.exists(file_or_dir): + return 0 + p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir], + stdout=subprocess.PIPE) + result = p.communicate() + m = re.match(r"^(\d+)\s.*", result[0]) + sz = int(m.group(1)) if m is not None else 0 + return sz + + def get_disk_utilization(self): + return self.get_disk_usage(self.dir_path) + + def get_disk_utilization_private_img(self): + return self.get_disk_usage(self.private_img) + + def get_private_img_sz(self): + if not os.path.exists(self.private_img): + return 0 + + return os.path.getsize(self.private_img) + + def create_xenstore_entries(self, xid): + if dry_run: + return + + # Set Xen Store entires with VM networking info: + + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_vm_type".format(xid), + self.type]) + + if self.is_netvm(): + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_netvm_gateway".format(xid), + self.gateway]) + + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_netvm_netmask".format(xid), + self.netmask]) + + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_netvm_network".format(xid), + self.network]) + + elif self.netvm_vm is not None: + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_ip".format(xid), + self.ip]) + + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_netmask".format(xid), + self.netmask]) + + retcode = subprocess.check_call ([ + "/usr/bin/xenstore-write", + "/local/domain/{0}/qubes_gateway".format(xid), + self.gateway]) + else: + pass + + + def start(self, debug_console = False, verbose = False): + if dry_run: + return + + if self.is_running(): + raise QubesException ("VM is already running!") + + if verbose: + print "--> Rereading the VM's conf file ({0})...".format(self.conf_file) + self.update_xen_storage() + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + if verbose: + print "--> Loading the VM (type = {0})...".format(self.type) + + mem_required = self.get_mem_static_max() + dom0_mem = dom0_vm.get_mem() + dom0_mem_new = dom0_mem - mem_required + if verbose: + print "--> AppVM required mem : {0}".format(mem_required) + print "--> Dom0 mem after launch : {0}".format(dom0_mem_new) + + if dom0_mem_new < dom0_min_memory: + raise MemoryError ("ERROR: starting this VM would cause Dom0 memory to go below {0}B".format(dom0_min_memory)) + + session.xenapi.VM.start (uuid, True) # Starting a VM paused + xid = int (session.xenapi.VM.get_domid (uuid)) + + if verbose: + print "--> Setting Xen Store info for the VM..." + self.create_xenstore_entries(xid) + + if not self.is_netvm() and self.netvm_vm is not None: + assert self.netvm_vm is not None + if verbose: + print "--> Attaching to the network backend (netvm={0})...".format(self.netvm_vm.name) + + if self.netvm_vm.qid != 0: + if not self.netvm_vm.is_running(): + print "ERROR: NetVM not running, please start it first" + self.force_shutdown() + raise QubesException ("NetVM not running") + retcode = subprocess.call (["/usr/sbin/xm", "network-attach", self.name, "backend={0}".format(self.netvm_vm.name)]) + if retcode != 0: + self.force_shutdown() + raise OSError ("ERROR: Cannot attach to network backend!") + + else: + retcode = subprocess.call (["/usr/sbin/xm", "network-attach", self.name, "backend=0"]) + if retcode != 0: + self.force_shutdown() + raise OSError ("ERROR: Cannot attach to network backend!") + + if verbose: + print "--> Starting the VM..." + session.xenapi.VM.unpause (uuid) + + # perhaps we should move it before unpause and fork? + if debug_console: + from xen.xm import console + if verbose: + print "--> Starting debug console..." + console.execConsole (xid) + + return xid + + def force_shutdown(self): + if dry_run: + return + + + session = get_xend_session_new_api() + uuids = session.xenapi.VM.get_by_name_label (self.name) + uuid = uuids[0] + session.xenapi.VM.hard_shutdown (uuid) + + def remove_from_disk(self): + if dry_run: + return + + + shutil.rmtree (self.dir_path) + + +class QubesTemplateVm(QubesVm): + """ + A class that represents an TemplateVM. A child of QubesVM. + """ + def __init__(self, **kwargs): + + if "dir_path" not in kwargs or kwargs["dir_path"] is None: + kwargs["dir_path"] = qubes_templates_dir + "/" + kwargs["name"] + + if "updateable" not in kwargs or kwargs["updateable"] is None : + kwargs["updateable"] = True + + root_img = kwargs.pop("root_img") if "root_img" in kwargs else None + private_img = kwargs.pop("private_img") if "private_img" in kwargs else None + appvms_conf_file = kwargs.pop("appvms_conf_file") if "appvms_conf_file" in kwargs else None + + super(QubesTemplateVm, self).__init__(type=VM_TEMPLATE, label = default_template_label, **kwargs) + + dir_path = kwargs["dir_path"] + + # TempleteVM doesn't use root-cow image + if root_img is not None and os.path.isabs(root_img): + self.root_img = root_img + else: + self.root_img = dir_path + "/" + ( + root_img if root_img is not None else default_root_img) + + if private_img is not None and os.path.isabs(private_img): + self.private_img = private_img + else: + self.private_img = dir_path + "/" + ( + private_img if private_img is not None else default_private_img) + + if appvms_conf_file is not None and os.path.isabs(appvms_conf_file): + self.appvms_conf_file = appvms_conf_file + else: + self.appvms_conf_file = dir_path + "/" + ( + appvms_conf_file if appvms_conf_file is not None else default_appvms_conf_file) + + self.templatevm_conf_template = self.dir_path + "/" + default_templatevm_conf_template + self.kernels_dir = self.dir_path + "/" + default_kernels_subdir + self.appmenus_templates_dir = self.dir_path + "/" + default_appmenus_templates_subdir + self.appvms = QubesVmCollection() + + def set_updateable(self): + if self.is_updateable(): + return + + assert not self.is_running() + # Make sure that all the AppVMs are non-updateable... + for appvm in self.appvms.values(): + if appvm.is_updateable(): + raise QubesException("One of the AppVMs ('{0}')is also 'updateable'\ + -- cannot make the TempleteVM {'{1}'} 'nonupdatable'".\ + format (appvm.name, self.name)) + self.updateable = True + + + def clone_disk_files(self, src_template_vm, verbose): + if dry_run: + return + + + assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!" + + if verbose: + print "--> Creating directory: {0}".format(self.dir_path) + os.mkdir (self.dir_path) + + if verbose: + print "--> Copying the VM config file:\n{0} =*>\n{1}".\ + format(src_template_vm.templatevm_conf_template, self.conf_file) + conf_templatevm_template = open (src_template_vm.templatevm_conf_template, "r") + conf_file = open(self.conf_file, "w") + rx_templatename = re.compile (r"%TEMPLATENAME%") + + for line in conf_templatevm_template: + line = rx_templatename.sub (self.name, line) + conf_file.write(line) + + conf_templatevm_template.close() + conf_file.close() + + if verbose: + print "--> Copying the VM config template :\n{0} ==>\n{1}".\ + format(src_template_vm.templatevm_conf_template, self.templatevm_conf_template) + shutil.copy (src_template_vm.templatevm_conf_template, self.templatevm_conf_template) + + if verbose: + print "--> Copying the VM config template :\n{0} ==>\n{1}".\ + format(src_template_vm.appvms_conf_file, self.appvms_conf_file) + shutil.copy (src_template_vm.appvms_conf_file, self.appvms_conf_file) + + if verbose: + print "--> Copying the template's private image:\n{0} ==>\n{1}".\ + format(src_template_vm.private_img, self.private_img) + # We prefer to use Linux's cp, because it nicely handles sparse files + retcode = subprocess.call (["cp", src_template_vm.private_img, self.private_img]) + retcode = 0 + if retcode != 0: + raise IOError ("Error while copying {0} to {1}".\ + format(src_template_vm.private_img, self.private_img)) + + if verbose: + print "--> Copying the template's root image:\n{0} ==>\n{1}".\ + format(src_template_vm.root_img, self.root_img) + # We prefer to use Linux's cp, because it nicely handles sparse files + retcode = subprocess.call (["cp", src_template_vm.root_img, self.root_img]) + retcode = 0 + if retcode != 0: + raise IOError ("Error while copying {0} to {1}".\ + format(src_template_vm.root_img, self.root_img)) + if verbose: + print "--> Copying the template's kernel dir:\n{0} ==>\n{1}".\ + format(src_template_vm.kernels_dir, self.kernels_dir) + shutil.copytree (src_template_vm.kernels_dir, self.kernels_dir) + + if verbose: + print "--> Copying the template's appvm templates dir:\n{0} ==>\n{1}".\ + format(src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir) + shutil.copytree (src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir) + + + def get_disk_utilization_root_img(self): + return self.get_disk_usage(self.root_img) + + def get_root_img_sz(self): + if not os.path.exists(self.root_img): + return 0 + + return os.path.getsize(self.root_img) + + def verify_files(self): + if dry_run: + return + + + if not os.path.exists (self.dir_path): + raise QubesException ( + "VM directory doesn't exist: {0}".\ + format(self.dir_path)) + + if not os.path.exists (self.conf_file): + raise QubesException ( + "VM config file doesn't exist: {0}".\ + format(self.conf_file)) + + if not os.path.exists (self.appvms_conf_file): + raise QubesException ( + "Appvm template config file doesn't exist: {0}".\ + format(self.appvms_conf_file)) + + if not os.path.exists (self.root_img): + raise QubesException ( + "VM root image file doesn't exist: {0}".\ + format(self.root_img)) + + if not os.path.exists (self.private_img): + raise QubesException ( + "VM private image file doesn't exist: {0}".\ + format(self.private_img)) + + if not os.path.exists (self.kernels_dir): + raise QubesException ( + "VM's kernels directory does not exist: {0}".\ + format(self.kernels_dir)) + + return True + + def start(self, debug_console = False, verbose = False): + if dry_run: + return + + + if not self.is_updateable(): + raise QubesException ("Cannot start Template VM that is marked \"nonupdatable\"") + + # First ensure that none of our appvms is running: + for appvm in self.appvms.values(): + if appvm.is_running(): + raise QubesException ("Cannot start TemplateVM when one of its AppVMs is running!") + + return super(QubesTemplateVm, self).start(debug_console=debug_console, verbose=verbose) + + def create_xml_element(self): + element = xml.etree.ElementTree.Element( + "QubesTemplateVm", + qid=str(self.qid), + name=self.name, + dir_path=self.dir_path, + conf_file=self.conf_file, + appvms_conf_file=self.appvms_conf_file, + root_img=self.root_img, + private_img=self.private_img, + uses_default_netvm=str(self.uses_default_netvm), + netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none", + installed_by_rpm=str(self.installed_by_rpm), + updateable=str(self.updateable), + ) + return element + +class QubesServiceVm(QubesVm): + """ + A class that represents a ServiceVM, e.g. NetVM. A child of QubesVM. + """ + def __init__(self, **kwargs): + + if "dir_path" not in kwargs or kwargs["dir_path"] is None: + kwargs["dir_path"] = qubes_servicevms_dir + "/" + kwargs["name"] + + root_img = kwargs.pop("root_img") if "root_img" in kwargs else None + private_img = kwargs.pop("private_img") if "private_img" in kwargs else None + + kwargs["updateable"] = True + super(QubesServiceVm, self).__init__(**kwargs) + + dir_path = kwargs["dir_path"] + assert dir_path is not None + + if root_img is not None and os.path.isabs(root_img): + self.root_img = root_img + else: + self.root_img = dir_path + "/" + ( + root_img if root_img is not None else default_root_img) + + if private_img is not None and os.path.isabs(private_img): + self.private_img = private_img + else: + self.private_img = dir_path + "/" + ( + private_img if private_img is not None else default_private_img) + + def set_updateable(self): + if self.is_updateable(): + return + # ServiceVMs are standalone, we can always make it updateable + # In fact there is no point in making it unpdatebale... + # So, this is just for completncess + assert not self.is_running() + self.updateable = True + + def get_disk_utilization_root_img(self): + return self.get_disk_usage(self.root_img) + + def get_root_img_sz(self): + if not os.path.exists(self.root_img): + return 0 + + return os.path.getsize(self.root_img) + + def verify_files(self): + if dry_run: + return + + + if not os.path.exists (self.dir_path): + raise QubesException ( + "VM directory doesn't exist: {0}".\ + format(self.dir_path)) + + if not os.path.exists (self.conf_file): + raise QubesException ( + "VM config file doesn't exist: {0}".\ + format(self.conf_file)) + + if not os.path.exists (self.root_img): + raise QubesException ( + "VM root image file doesn't exist: {0}".\ + format(self.root_img)) + + return True + + def create_xml_element(self): + raise NotImplementedError + +class QubesNetVm(QubesServiceVm): + """ + A class that represents a NetVM. A child of QubesServiceVM. + """ + def __init__(self, **kwargs): + netid = kwargs.pop("netid") + self.netid = netid + self.__network = "10.{0}.0.0".format(netid) + self.netprefix = "10.{0}.".format(netid) + self.__netmask = vm_default_netmask + self.__gateway = self.netprefix + "0.1" + + if "label" not in kwargs or kwargs["label"] is None: + kwargs["label"] = default_servicevm_label + super(QubesNetVm, self).__init__(type=VM_NETVM, installed_by_rpm=True, **kwargs) + + @property + def gateway(self): + return self.__gateway + + @property + def netmask(self): + return self.__netmask + + @property + def network(self): + return self.__network + + def get_ip_for_vm(self, qid): + hi = qid / 253 + lo = qid % 253 + 2 + assert hi >= 0 and hi <= 254 and lo >= 2 and lo <= 254, "Wrong IP address for VM" + return self.netprefix + "{0}.{1}".format(hi,lo) + + def create_xml_element(self): + element = xml.etree.ElementTree.Element( + "QubesNetVm", + qid=str(self.qid), + netid=str(self.netid), + name=self.name, + dir_path=self.dir_path, + conf_file=self.conf_file, + root_img=self.root_img, + private_img=self.private_img, + installed_by_rpm=str(self.installed_by_rpm), + ) + return element + +class QubesDom0NetVm(QubesNetVm): + def __init__(self): + super(QubesDom0NetVm, self).__init__(qid=0, name="dom0", netid=0, + dir_path=None, root_img = None, + private_img = None, + label = default_template_label) + + def is_running(self): + return True + + def get_cpu_total_load(self): + if dry_run: + import random + return random.random() * 100 + + session = get_xend_session_new_api() + hosts = session.xenapi.host.get_all() + cpus = session.xenapi.host.get_host_CPUs(hosts[0]) + cpu_total_load = 0.0 + for cpu in cpus: + cpu_total_load += session.xenapi.host_cpu.get_utilisation(cpu) + cpu_total_load /= len(cpus) + p = 100*cpu_total_load + if p > 100: + p = 100 + return p + + def get_mem(self): + + # Unfortunately XenAPI provides only info about total memory, not the one actually usable by Dom0... + #session = get_xend_session_new_api() + #hosts = session.xenapi.host.get_all() + #metrics = session.xenapi.host.get_metrics(hosts[0]) + #memory_total = int(session.xenapi.metrics.get_memory_total(metrics)) + + # ... so we must read /proc/meminfo, just like free command does + f = open ("/proc/meminfo") + for line in f: + match = re.match(r"^MemTotal\:\s*(\d+) kB", line) + if match is not None: + break + f.close() + assert match is not None + return int(match.group(1))*1024 + + def get_xid(self): + return 0 + + def get_power_state(self): + return "Running" + + def get_disk_usage(self, file_or_dir): + return 0 + + def get_disk_utilization(self): + return 0 + + def get_disk_utilization_private_img(self): + return 0 + + def get_private_img_sz(self): + return 0 + + def start(self, debug_console = False, verbose = False): + raise QubesException ("Cannot start Dom0 fake domain!") + + def create_xml_element(self): + return None + + def verify_files(self): + return True + + +class QubesAppVm(QubesVm): + """ + A class that represents an AppVM. A child of QubesVM. + """ + def __init__(self, **kwargs): + + if "dir_path" not in kwargs or kwargs["dir_path"] is None: + kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"] + + if "updateable" not in kwargs or kwargs["updateable"] is None: + kwargs["updateable"] = False + + private_img = kwargs.pop("private_img") + template_vm = kwargs.pop("template_vm") + + + super(QubesAppVm, self).__init__(type=VM_APPVM, **kwargs) + qid = kwargs["qid"] + dir_path = kwargs["dir_path"] + + assert template_vm is not None, "Missing template_vm for AppVM!" + if not template_vm.is_templete(): + print "ERROR: template_qid={0} doesn't point to a valid TempleteVM".\ + format(new_vm.template_vm.qid) + return False + + self.template_vm = template_vm + template_vm.appvms[qid] = self + + # AppVM doesn't have its own root_img, it uses the one provided by the TemplateVM + if private_img is not None and os.path.isabs(private_img): + self.private_img = private_img + else: + self.private_img = dir_path + "/" + ( + private_img if private_img is not None else default_private_img) + + self.rootcow_img = dir_path + "/" + default_rootcow_img + self.swapcow_img = dir_path + "/" + default_swapcow_img + + + def set_updateable(self): + if self.is_updateable(): + return + + assert not self.is_running() + # Check if the TemaplteVM is *non* updatable... + if not self.template_vm.is_updateable(): + self.updateable = True + self.reset_cow_storage() + else: + # Temaplate VM is Updatable itself --> can't make the AppVM updateable too + # as this would cause COW-backed storage incoherency + raise QubesException ("TemaplteVM is updateable: cannot make the AppVM '{0}' updateable".format(self.name)) + + def create_on_disk(self, verbose): + if dry_run: + return + + + if verbose: + print "--> Creating directory: {0}".format(self.dir_path) + os.mkdir (self.dir_path) + + if verbose: + print "--> Creating the VM config file: {0}".format(self.conf_file) + + conf_template = open (self.template_vm.appvms_conf_file, "r") + conf_appvm = open(self.conf_file, "w") + rx_vmname = re.compile (r"%VMNAME%") + rx_vmdir = re.compile (r"%VMDIR%") + rx_template = re.compile (r"%TEMPLATEDIR%") + + for line in conf_template: + line = rx_vmname.sub (self.name, line) + line = rx_vmdir.sub (self.dir_path, line) + line = rx_template.sub (self.template_vm.dir_path, line) + conf_appvm.write(line) + + conf_template.close() + conf_appvm.close() + + template_priv = self.template_vm.private_img + if verbose: + print "--> Copying the template's private image: {0}".\ + format(template_priv) + + # We prefer to use Linux's cp, because it nicely handles sparse files + retcode = subprocess.call (["cp", template_priv, self.private_img]) + if retcode != 0: + raise IOError ("Error while copying {0} to {1}".\ + format(template_priv, self.private_img)) + + if verbose: + print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path) + os.symlink (self.label.icon_path, self.icon_path) + + subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name]) + + def get_disk_utilization_root_img(self): + return 0 + + def get_root_img_sz(self): + return 0 + + + def verify_files(self): + if dry_run: + return + + + if not os.path.exists (self.dir_path): + raise QubesException ( + "VM directory doesn't exist: {0}".\ + format(self.dir_path)) + + if not os.path.exists (self.conf_file): + raise QubesException ( + "VM config file doesn't exist: {0}".\ + format(self.conf_file)) + + if not os.path.exists (self.private_img): + raise QubesException ( + "VM private image file doesn't exist: {0}".\ + format(self.private_img)) + return True + + + def create_xml_element(self): + element = xml.etree.ElementTree.Element( + "QubesAppVm", + qid=str(self.qid), + name=self.name, + dir_path=self.dir_path, + conf_file=self.conf_file, + template_qid=str(self.template_vm.qid), + uses_default_netvm=str(self.uses_default_netvm), + netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none", + private_img=self.private_img, + installed_by_rpm=str(self.installed_by_rpm), + updateable=str(self.updateable), + label=self.label.name) + return element + + def start(self, debug_console = False, verbose = False): + if dry_run: + return + + if self.is_running(): + raise QubesException("VM is already running!") + + # First ensure that our template is *not* running: + if self.template_vm.is_running(): + raise QubesException ("Cannot start AppVM when its template is running!") + + if not self.is_updateable(): + self.reset_cow_storage() + + return super(QubesAppVm, self).start(debug_console=debug_console, verbose=verbose) + + def reset_cow_storage (self): + + print "--> Resetting the COW storage: {0}...".format (self.rootcow_img) + + if dry_run: + return + # this is probbaly not needed, as open (..., "w") should remove the previous file + if os.path.exists (self.rootcow_img): + os.remove (self.rootcow_img) + + + f_cow = open (self.rootcow_img, "w") + f_root = open (self.template_vm.root_img, "r") + f_root.seek(0, os.SEEK_END) + f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img + f_cow.close () + f_root.close() + + print "--> Resetting the COW storage: {0}...".format (self.swapcow_img) + if os.path.exists (self.swapcow_img): + os.remove (self.swapcow_img) + + f_swap_cow = open (self.swapcow_img, "w") + f_swap_cow.truncate (swap_cow_sz) + f_swap_cow.close() + + def remove_from_disk(self): + if dry_run: + return + + + subprocess.check_call ([qubes_appmenu_remove_cmd, self.name]) + shutil.rmtree (self.dir_path) + + + +class QubesVmCollection(dict): + """ + A collection of Qubes VMs indexed by Qubes id (qid) + """ + + def __init__(self): + super(QubesVmCollection, self).__init__() + self.default_netvm_qid = None + self.default_template_qid = None + + def values(self): + for qid in self.keys(): + yield self[qid] + + def items(self): + for qid in self.keys(): + yield (qid, self[qid]) + + def __iter__(self): + for qid in sorted(super(QubesVmCollection, self).keys()): + yield qid + + keys = __iter__ + + def __setitem__(self, key, value): + if key not in self: + return super(QubesVmCollection, self).__setitem__(key, value) + else: + assert False, "Attempt to add VM with qid that already exists in the collection!" + + + def add_new_appvm(self, name, template_vm, + dir_path = None, conf_file = None, + private_img = None, + label = None): + + qid = self.get_new_unused_qid() + vm = QubesAppVm (qid=qid, name=name, template_vm=template_vm, + dir_path=dir_path, conf_file=conf_file, + private_img=private_img, + netvm_vm = self.get_default_netvm_vm(), + label=label) + + if not self.verify_new_vm (vm): + assert False, "Wrong VM description!" + self[vm.qid]=vm + return vm + + def add_new_templatevm(self, name, + dir_path = None, conf_file = None, + root_img = None, private_img = None, + installed_by_rpm = True): + + qid = self.get_new_unused_qid() + vm = QubesTemplateVm (qid=qid, name=name, + dir_path=dir_path, conf_file=conf_file, + root_img=root_img, private_img=private_img, + installed_by_rpm=installed_by_rpm, + netvm_vm = self.get_default_netvm_vm()) + + if not self.verify_new_vm (vm): + assert False, "Wrong VM description!" + self[vm.qid]=vm + + if self.default_template_qid is None: + self.set_default_template_vm(vm) + + return vm + + def clone_templatevm(self, src_template_vm, name, dir_path = None, verbose = False): + + assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!" + + vm = self.add_new_templatevm (name=name, dir_path=dir_path, installed_by_rpm = False) + + return vm + + + def add_new_netvm(self, name, + dir_path = None, conf_file = None, + root_img = None): + + qid = self.get_new_unused_qid() + netid = self.get_new_unused_netid() + vm = QubesNetVm (qid=qid, name=name, + netid=netid, + dir_path=dir_path, conf_file=conf_file, + root_img=root_img) + + if not self.verify_new_vm (vm): + assert False, "Wrong VM description!" + self[vm.qid]=vm + + if self.default_netvm_qid is None: + self.set_default_netvm_vm(vm) + + return vm + + def set_default_template_vm(self, vm): + assert vm.is_templete(), "VM {0} is not a TempleteVM!".format(vm.name) + self.default_template_qid = vm.qid + + def get_default_template_vm(self): + if self.default_template_qid is None: + return None + else: + return self[self.default_template_qid] + + def set_default_netvm_vm(self, vm): + assert vm.is_netvm(), "VM {0} is not a NetVM!".format(vm.name) + self.default_netvm_qid = vm.qid + + def get_default_netvm_vm(self): + if self.default_netvm_qid is None: + return None + else: + return self[self.default_netvm_qid] + + def get_vm_by_name(self, name): + for vm in self.values(): + if (vm.name == name): + return vm + return None + + def get_qid_by_name(self, name): + vm = self.get_vm_by_name(name) + return vm.qid if vm is not None else None + + def get_vms_based_on(self, template_qid): + vms = set([vm for vm in self.values() + if (vm.is_appvm() and vm.template_vm.qid == template_qid)]) + return vms + + def verify_new_vm(self, new_vm): + + # Verify that qid is unique + for vm in self.values(): + if vm.qid == new_vm.qid: + print "ERROR: The qid={0} is already used by VM '{1}'!".\ + format(vm.qid, vm.name) + return False + + # Verify that name is unique + for vm in self.values(): + if vm.name == new_vm.name: + print "ERROR: The name={0} is already used by other VM with qid='{1}'!".\ + format(vm.name, vm.qid) + return False + + return True + + def get_new_unused_qid(self): + used_ids = set([vm.qid for vm in self.values()]) + for id in range (1, qubes_max_qid): + if id not in used_ids: + return id + raise LookupError ("Cannot find unused qid!") + + def get_new_unused_netid(self): + used_ids = set([vm.netid for vm in self.values() if vm.is_netvm()]) + for id in range (1, qubes_max_netid): + if id not in used_ids: + return id + raise LookupError ("Cannot find unused netid!") + + + def check_if_storage_exists(self): + try: + f = open (qubes_store_filename, 'r') + except IOError: + return False + f.close() + return True + + def create_empty_storage(self): + self.qubes_store_file = open (qubes_store_filename, 'w') + self.clear() + self.save() + + def lock_db_for_reading(self): + self.qubes_store_file = open (qubes_store_filename, 'r') + fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH) + + def lock_db_for_writing(self): + self.qubes_store_file = open (qubes_store_filename, 'r+') + fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX) + + def unlock_db(self): + fcntl.lockf (self.qubes_store_file, fcntl.LOCK_UN) + self.qubes_store_file.close() + + def save(self): + root = xml.etree.ElementTree.Element( + "QubesVmCollection", + + default_template=str(self.default_template_qid) \ + if self.default_template_qid is not None else "None", + + default_netvm=str(self.default_netvm_qid) \ + if self.default_netvm_qid is not None else "None" + ) + + for vm in self.values(): + element = vm.create_xml_element() + if element is not None: + root.append(element) + tree = xml.etree.ElementTree.ElementTree(root) + + try: + + # We need to manually truncate the file, as we open the + # file as "r+" in the lock_db_for_writing() function + self.qubes_store_file.seek (0, os.SEEK_SET) + self.qubes_store_file.truncate() + tree.write(self.qubes_store_file, "UTF-8") + except EnvironmentError as err: + print("{0}: import error: {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + return True + + def load(self): + self.clear() + + dom0vm = QubesDom0NetVm () + self[dom0vm.qid] = dom0vm + self.default_netvm_qid = 0 + + global dom0_vm + dom0_vm = dom0vm + + try: + tree = xml.etree.ElementTree.parse(self.qubes_store_file) + except (EnvironmentError, + xml.parsers.expat.ExpatError) as err: + print("{0}: import error: {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + + element = tree.getroot() + default_template = element.get("default_template") + self.default_template_qid = int(default_template) \ + if default_template != "None" else None + + default_netvm = element.get("default_netvm") + if default_netvm is not None: + self.default_netvm_qid = int(default_netvm) \ + if default_netvm != "None" else None + #assert self.default_netvm_qid is not None + + # Read in the NetVMs first, because a reference to NetVM + # is needed to create all other VMs + for element in tree.findall("QubesNetVm"): + try: + kwargs = {} + attr_list = ("qid", "netid", "name", "dir_path", "conf_file", + "private_img", "root_img", + ) + + for attribute in attr_list: + kwargs[attribute] = element.get(attribute) + + kwargs["qid"] = int(kwargs["qid"]) + kwargs["netid"] = int(kwargs["netid"]) + + vm = QubesNetVm(**kwargs) + self[vm.qid] = vm + + except (ValueError, LookupError) as err: + print("{0}: import error (QubesNetVM) {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + + + self.default_template_qid + # Then, read in the TemplateVMs, because a reference to templete VM + # is needed to create each AppVM + for element in tree.findall("QubesTemplateVm"): + try: + + kwargs = {} + attr_list = ("qid", "name", "dir_path", "conf_file", + "appvms_conf_file", "private_img", "root_img", + "installed_by_rpm", "updateable", + "uses_default_netvm", "netvm_qid") + + for attribute in attr_list: + kwargs[attribute] = element.get(attribute) + + kwargs["qid"] = int(kwargs["qid"]) + kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False + if kwargs["updateable"] is not None: + kwargs["updateable"] = True if kwargs["updateable"] == "True" else False + + if "uses_default_netvm" not in kwargs: + kwargs["uses_default_netvm"] = True + else: + kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False + if kwargs["uses_default_netvm"] is True: + netvm_vm = self.get_default_netvm_vm() + kwargs.pop("netvm_qid") + else: + if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None: + netvm_vm = None + kwargs.pop("netvm_qid") + else: + netvm_qid = int(kwargs.pop("netvm_qid")) + if netvm_qid not in self: + netvm_vm = None + else: + netvm_vm = self[netvm_qid] + + kwargs["netvm_vm"] = netvm_vm + + vm = QubesTemplateVm(**kwargs) + + self[vm.qid] = vm + except (ValueError, LookupError) as err: + print("{0}: import error (QubesTemplateVm): {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + + # Finally, read in the AppVMs + for element in tree.findall("QubesAppVm"): + try: + kwargs = {} + attr_list = ("qid", "name", "dir_path", "conf_file", + "private_img", "template_qid", + "updateable", "label", "netvm_qid", + "uses_default_netvm") + + for attribute in attr_list: + kwargs[attribute] = element.get(attribute) + + kwargs["qid"] = int(kwargs["qid"]) + kwargs["template_qid"] = int(kwargs["template_qid"]) + if kwargs["updateable"] is not None: + kwargs["updateable"] = True if kwargs["updateable"] == "True" else False + + template_vm = self[kwargs.pop("template_qid")] + if template_vm is None: + print "ERROR: AppVM '{0}' uses unkown template qid='{1}'!".\ + format(kwargs["name"], kwargs["template_qid"]) + + kwargs["template_vm"] = template_vm + + if "uses_default_netvm" not in kwargs: + kwargs["uses_default_netvm"] = True + else: + kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False + if kwargs["uses_default_netvm"] is True: + netvm_vm = self.get_default_netvm_vm() + kwargs.pop("netvm_qid") + else: + if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None: + netvm_vm = None + kwargs.pop("netvm_qid") + else: + netvm_qid = int(kwargs.pop("netvm_qid")) + if netvm_qid not in self: + netvm_vm = None + else: + netvm_vm = self[netvm_qid] + + kwargs["netvm_vm"] = netvm_vm + + if kwargs["label"] is not None: + if kwargs["label"] not in QubesVmLabels: + print "ERROR: incorrect label for VM '{0}'".format(kwargs["name"]) + kwargs.pop ("label") + else: + kwargs["label"] = QubesVmLabels[kwargs["label"]] + + vm = QubesAppVm(**kwargs) + + self[vm.qid] = vm + except (ValueError, LookupError) as err: + print("{0}: import error (QubesAppVm): {1}".format( + os.path.basename(sys.argv[0]), err)) + return False + + return True + + + + +class QubesDaemonPidfile(object): + def __init__(self, name): + self.name = name + self.path = "/var/run/qubes/" + name + ".pid" + + def create_pidfile(self): + f = open (self.path, 'w') + f.write(str(os.getpid())) + f.close() + + def pidfile_exists(self): + return os.path.exists(self.path) + + def read_pid(self): + f = open (self.path) + pid = f.read ().strip() + f.close() + return int(pid) + + def pidfile_is_stale(self): + if not self.pidfile_exists(): + return False + + # check if the pid file is valid... + proc_path = "/proc/" + str(self.read_pid()) + "/cmdline" + if not os.path.exists (proc_path): + print "Path {0} doesn't exist, assuming stale pidfile.".format(proc_path) + return True + + f = open (proc_path) + cmdline = f.read () + f.close() + +# The following doesn't work with python -- one would have to get argv[1] and compare it with self.name... +# if not cmdline.strip().endswith(self.name): +# print "{0} = {1} doesn't seem to point to our process ({2}), assuming stale pidile.".format(proc_path, cmdline, self.name) +# return True + + return False # It's a good pidfile + + def remove_pidfile(self): + os.remove (self.path) + + def __enter__ (self): + # assumes the pidfile doesn't exist -- you should ensure it before opening the context + self.create_pidfile() + def __exit__ (self): + self.remove_pidfile() + + diff --git a/dom0/qvm-tools/qvm-add-appvm b/dom0/qvm-tools/qvm-add-appvm new file mode 100755 index 00000000..b39632d0 --- /dev/null +++ b/dom0/qvm-tools/qvm-add-appvm @@ -0,0 +1,82 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser; + +def main(): + usage = "usage: %prog [options] \n\n"\ + "Adds an already installed appvm to the Qubes DB\n"\ + "WARNING: Noramlly you would not need this command,\n"\ + "and you would use qvm-create instead!" + + parser = OptionParser (usage) + parser.add_option ("-p", "--path", dest="dir_path", + help="Specify path to the template directory") + parser.add_option ("-c", "--conf", dest="conf_file", + help="Specify the Xen VM .conf file to use\ + (relative to the template dir path)") + + (options, args) = parser.parse_args () + if (len (args) != 2): + parser.error ("You must specify at least the AppVM and TemplateVM names!") + vmname = args[0] + templatename = args[1] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + if qvm_collection.get_vm_by_name(vmname) is not None: + print "ERROR: A VM with the name '{0}' already exists in the system.".format(vmname) + exit(1) + + template_vm = qvm_collection.get_vm_by_name(templatename) + if template_vm is None: + print "ERROR: A Template VM with the name '{0}' does not exist in the system.".format(templatename) + exit(1) + + + vm = qvm_collection.add_new_appvm(vmname, template_vm, + conf_file=options.conf_file, + dir_path=options.dir_path) + + try: + vm.verify_files() + except QubesException as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + try: + vm.add_to_xen_storage() + + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-add-netvm b/dom0/qvm-tools/qvm-add-netvm new file mode 100755 index 00000000..f5d3cde4 --- /dev/null +++ b/dom0/qvm-tools/qvm-add-netvm @@ -0,0 +1,119 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from optparse import OptionParser +import subprocess +import shutil +import re + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException + +def find_net_devices(): + p = subprocess.Popen (["lspci", "-mm", "-n"], stdout=subprocess.PIPE) + result = p.communicate() + retcode = p.returncode + if (retcode != 0): + print "ERROR when executing lspci!" + raise IOError + + net_devices = set() + rx_netdev = re.compile (r"^([0-9][0-9]:[0-9][0-9].[0-9]) \"02") + for dev in str(result[0]).splitlines(): + match = rx_netdev.match (dev) + if match is not None: + dev_bdf = match.group(1) + assert dev_bdf is not None + net_devices.add (dev_bdf) + + return net_devices + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + parser.add_option ("-p", "--path", dest="dir_path", + help="Specify path to the template directory") + parser.add_option ("-c", "--conf", dest="conf_file", + help="Specify the Xen VM .conf file to use\ + (relative to the template dir path)") + + + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify a NetVM name!") + netvmname = args[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + if qvm_collection.get_vm_by_name(netvmname) is not None: + print "ERROR: A VM with the name '{0}' already exists in the system.".format(netvmname) + exit(1) + + vm = qvm_collection.add_new_netvm(netvmname, + conf_file=options.conf_file, + dir_path=options.dir_path) + + try: + vm.verify_files() + except QubesException as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + + net_devices = find_net_devices() + print "Found the following net devices in your system:" + dev_str = '' + for dev in net_devices: + print "--> {0}".format(dev) + dev_str += '"{0}", '.format(dev) + + print "Assigning them to the netvm '{0}'".format(netvmname) + rx_pcidevs = re.compile (r"%NETVMPCIDEVS%") + conf_template = open (vm.conf_file, "r") + conf_vm = open(vm.conf_file + ".processed", "w") + + for line in conf_template: + line = rx_pcidevs.sub(dev_str, line) + conf_vm.write(line) + + conf_template.close() + conf_vm.close() + + shutil.move (vm.conf_file + ".processed", vm.conf_file) + + + try: + pass + vm.add_to_xen_storage() + + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-add-template b/dom0/qvm-tools/qvm-add-template new file mode 100755 index 00000000..a6a58a12 --- /dev/null +++ b/dom0/qvm-tools/qvm-add-template @@ -0,0 +1,78 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser; + +def main(): + usage = "usage: %prog [options] \n"\ + "Adds an already installed template to the Qubes DB" + + parser = OptionParser (usage) + parser.add_option ("-p", "--path", dest="dir_path", + help="Specify path to the template directory") + parser.add_option ("-c", "--conf", dest="conf_file", + help="Specify the Xen VM .conf file to use\ + (relative to the template dir path)") + + parser.add_option ("--rpm", action="store_true", dest="installed_by_rpm", + help="Template files have been installed by RPM", default=False) + + + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify at least the TemplateVM name!") + vmname = args[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + if qvm_collection.get_vm_by_name(vmname) is not None: + print "ERROR: A VM with the name '{0}' already exists in the system.".format(vmname) + exit(1) + + vm = qvm_collection.add_new_templatevm(vmname, + conf_file=options.conf_file, + dir_path=options.dir_path, + installed_by_rpm=options.installed_by_rpm) + + try: + vm.verify_files() + except QubesException as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + try: + vm.add_to_xen_storage() + + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(vm.qid) + exit (1) + + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-clone-template b/dom0/qvm-tools/qvm-clone-template new file mode 100755 index 00000000..b346012a --- /dev/null +++ b/dom0/qvm-tools/qvm-clone-template @@ -0,0 +1,75 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser; + +def main(): + usage = "usage: %prog [options] \n"\ + "Clones an existing template by copying all its disk files" + + parser = OptionParser (usage) + parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) + parser.add_option ("-p", "--path", dest="dir_path", + help="Specify path to the template directory") + + (options, args) = parser.parse_args () + if (len (args) != 2): + parser.error ("You must specify at least the src and dst TemplateVM names!") + srcname = args[0] + dstname = args[1] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + src_tvm = qvm_collection.get_vm_by_name(srcname) + if src_tvm is None: + print "ERROR: A VM with the name '{0}' does not exist in the system.".format(srcname) + exit(1) + + if qvm_collection.get_vm_by_name(dstname) is not None: + print "ERROR: A VM with the name '{0}' already exists in the system.".format(dstname) + exit(1) + + dst_tvm = qvm_collection.clone_templatevm(src_template_vm=src_tvm, + name=dstname, + dir_path=options.dir_path) + + try: + dst_tvm.clone_disk_files (src_template_vm=src_tvm, verbose=options.verbose) + + if options.verbose: + print "--> Adding to Xen Storage..." + + dst_tvm.add_to_xen_storage() + + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + qvm_collection.pop(dst_tvm.qid) + exit (1) + + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-create b/dom0/qvm-tools/qvm-create new file mode 100755 index 00000000..9ff9c6c0 --- /dev/null +++ b/dom0/qvm-tools/qvm-create @@ -0,0 +1,101 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesVmLabels +from optparse import OptionParser; +import subprocess + + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + parser.add_option ("-t", "--template", dest="template", + help="Specify the TemplateVM to use") + parser.add_option ("-l", "--label", dest="label", + help="Specify the label to use for the new VM (e.g. red, yellow, green, ...)") + + parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify VM name!") + vmname = args[0] + + if options.label is None: + print "You must choose a label for the new VM by passing the --label option." + print "Possible values are:" + for l in QubesVmLabels.values(): + print "* {0}".format(l.name) + exit (1) + + if options.label not in QubesVmLabels: + print "Wrong label name, supported values are the following:" + for l in QubesVmLabels.values(): + print "* {0}".format(l.name) + exit (1) + label = QubesVmLabels[options.label] + + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + + if qvm_collection.get_vm_by_name(vmname) is not None: + print "A VM with the name '{0}' already exists in the system.".format(vmname) + exit(1) + + if options.template is not None: + template_vm = qvm_collection.get_vm_by_name(options.template) + if template_vm is None: + print "There is no (Templete)VM with the name '{0}'".format(options.template) + exit (1) + if not template_vm.is_templete(): + print "VM '{0}' is not a TemplateVM".format(options.template) + exit (1) + if (options.verbose): + print "--> Using TemplateVM: {0}".format(template_vm.name) + + else: + if qvm_collection.get_default_template_vm() is None: + print "No default TempleteVM defined!" + exit (1) + else: + template_vm = qvm_collection.get_default_template_vm() + if (options.verbose): + print "--> Using default TemplateVM: {0}".format(template_vm.name) + + vm = qvm_collection.add_new_appvm(vmname, template_vm, label = label) + try: + vm.create_on_disk(verbose=options.verbose) + vm.add_to_xen_storage() + + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + vm.remove_from_disk() + exit (1) + + + qvm_collection.save() + qvm_collection.unlock_db() + + +main() diff --git a/dom0/qvm-tools/qvm-get-default-netvm b/dom0/qvm-tools/qvm-get-default-netvm new file mode 100755 index 00000000..977faea1 --- /dev/null +++ b/dom0/qvm-tools/qvm-get-default-netvm @@ -0,0 +1,39 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from optparse import OptionParser; + +def main(): + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + netvm = qvm_collection.get_default_netvm_vm() + if netvm is None: + print "" + else: + print netvm.name + + + +main() diff --git a/dom0/qvm-tools/qvm-init-storage b/dom0/qvm-tools/qvm-init-storage new file mode 100755 index 00000000..2ab75e91 --- /dev/null +++ b/dom0/qvm-tools/qvm-init-storage @@ -0,0 +1,32 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection + +def main(): + qvm_collection = QubesVmCollection() + if qvm_collection.check_if_storage_exists(): + print "Storage exists, not overwriting." + exit(1) + qvm_collection.create_empty_storage() + +main() diff --git a/dom0/qvm-tools/qvm-kill b/dom0/qvm-tools/qvm-kill new file mode 100755 index 00000000..689fff64 --- /dev/null +++ b/dom0/qvm-tools/qvm-kill @@ -0,0 +1,54 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from optparse import OptionParser; +import subprocess + +qubes_guid_path = "/usr/bin/qubes_guid" + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify VM name!") + vmname = args[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) + if vm is None: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + try: + vm.force_shutdown() + except (IOError, OSError) as err: + print "ERROR: {0}".format(err) + exit (1) + + +main() diff --git a/dom0/qvm-tools/qvm-ls b/dom0/qvm-tools/qvm-ls new file mode 100755 index 00000000..f5872a77 --- /dev/null +++ b/dom0/qvm-tools/qvm-ls @@ -0,0 +1,191 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser + + +fields = { + "qid": {"func": "vm.qid"}, + + "name": {"func": "('=>' if qvm_collection.get_default_template_vm() is not None\ + and vm.qid == qvm_collection.get_default_template_vm().qid else '')\ + + ('[' if vm.is_templete() else '')\ + + ('{' if vm.is_netvm() else '')\ + + vm.name \ + + (']' if vm.is_templete() else '')\ + + ('}' if vm.is_netvm() else '')"}, + + "type": {"func": "'Tpl' if vm.is_templete() else \ + (' Net' if vm.is_netvm() else '')"}, + + "updbl" : {"func": "'Yes' if vm.is_updateable() else ''"}, + + "template": {"func": "'n/a' if vm.is_templete() or vm.is_netvm() else\ + qvm_collection[vm.template_vm.qid].name"}, + + "netvm": {"func": "'n/a' if vm.is_netvm() else\ + ('*' if vm.uses_default_netvm else '') +\ + qvm_collection[vm.netvm_vm.qid].name\ + if vm.netvm_vm is not None else '-'"}, + + "ip" : {"func": "vm.ip"}, + "netmask" : {"func": "vm.netmask"}, + "gateway" : {"func": "vm.gateway"}, + + "xid" : {"func" : "vm.get_xid() if vm.is_running() else '-'"}, + + "mem" : {"func" : "(str(vm.get_mem()/1024/1024) + ' MB') if vm.is_running() else '-'"}, + "cpu" : {"func" : "round (vm.get_cpu_total_load(), 1) if vm.is_running() else '-'"}, + "disk": {"func" : "str(vm.get_disk_utilization()/(1024*1024)) + ' MB'"}, + "state": {"func" : "vm.get_power_state()"}, + + "priv-curr": {"func" : "str(vm.get_disk_utilization_private_img()/(1024*1024)) + ' MB'"}, + "priv-max": {"func" : "str(vm.get_private_img_sz()/(1024*1024)) + ' MB'"}, + "priv-util": {"func" : "str(vm.get_disk_utilization_private_img()*100/vm.get_private_img_sz()) + '%' if vm.get_private_img_sz() != 0 else '-'"}, + + "root-curr": {"func" : "str(vm.get_disk_utilization_root_img()/(1024*1024)) + ' MB'"}, + "root-max": {"func" : "str(vm.get_root_img_sz()/(1024*1024)) + ' MB'"}, + "root-util": {"func" : "str(vm.get_disk_utilization_root_img()*100/vm.get_root_img_sz()) + '%' if vm.get_root_img_sz() != 0 else '-'"}, + + "label" : {"func" : "vm.label.name"}, + + "on" : {"func" : "'*' if vm.is_running() else ''"} + +} + + + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + + parser.add_option ("-n", "--network", dest="network", + action="store_true", default=False, + help="Show network addresses assigned to VMs") + + parser.add_option ("-c", "--cpu", dest="cpu", + action="store_true", default=False, + help="Show CPU load") + + parser.add_option ("-m", "--mem", dest="mem", + action="store_true", default=False, + help="Show memory usage") + + parser.add_option ("-d", "--disk", dest="disk", + action="store_true", default=False, + help="Show VM disk utilization statistics") + + parser.add_option ("-i", "--ids", dest="ids", + action="store_true", default=False, + help="Show Qubes and Xen id#s") + + + (options, args) = parser.parse_args () + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + fields_to_display = ["name", "on", "state", "updbl", "type", "template", "netvm", "label" ] + + if (options.ids): + fields_to_display += ["qid", "xid"] + + if (options.cpu): + fields_to_display += ["cpu"] + + if (options.mem): + fields_to_display += ["mem"] + + if (options.network): + fields_to_display.remove ("template") + fields_to_display += ["ip", "netmask", "gateway"] + + if (options.disk): + fields_to_display.remove ("template") + fields_to_display.remove ("netvm") + fields_to_display += ["priv-curr", "priv-max", "root-curr", "root-max", "disk" ] + + + vms_list = [vm for vm in qvm_collection.values()] + no_vms = len (vms_list) + vms_to_display = [] + # Frist, the NetVMs... + for netvm in vms_list: + if netvm.is_netvm(): + vms_to_display.append (netvm) + + # Now, the template, and all its AppVMs... + for tvm in vms_list: + if tvm.is_templete(): + vms_to_display.append (tvm) + for appvm in vms_list: + if appvm.is_appvm() and appvm.template_vm.qid == tvm.qid: + vms_to_display.append(appvm) + + assert len(vms_to_display) == no_vms + + # First calculate the maximum width of each field we want to display + total_width = 0; + for f in fields_to_display: + fields[f]["max_width"] = len(f) + for vm in vms_to_display: + l = len(str(eval(fields[f]["func"]))) + if l > fields[f]["max_width"]: + fields[f]["max_width"] = l + total_width += fields[f]["max_width"] + + + # Display the header + s = "" + for f in fields_to_display: + fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1) + s += fmt.format('-') + print s + s = "" + for f in fields_to_display: + fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1) + s += fmt.format(f) + print s + s = "" + for f in fields_to_display: + fmt="{{0:-^{0}}}-+".format(fields[f]["max_width"] + 1) + s += fmt.format('-') + print s + + # ... and the actual data + for vm in vms_to_display: + s = "" + for f in fields_to_display: + fmt="{{0:>{0}}} |".format(fields[f]["max_width"] + 1) + s += fmt.format(eval(fields[f]["func"])) + print s + + try: + vm.verify_files() + except QubesException as err: + print "WARNING: VM '{0}' has corrupted files!".format(vm.name) + +main() diff --git a/dom0/qvm-tools/qvm-prefs b/dom0/qvm-tools/qvm-prefs new file mode 100755 index 00000000..f2c77367 --- /dev/null +++ b/dom0/qvm-tools/qvm-prefs @@ -0,0 +1,219 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesVmLabels +from optparse import OptionParser +import subprocess + +def do_list(vm): + label_width = 18 + fmt="{{0:<{0}}}: {{1}}".format(label_width) + + print fmt.format ("name", vm.name) + print fmt.format ("label", vm.label.name) + print fmt.format ("type", vm.type) + if vm.is_appvm(): + print fmt.format ("template", vm.template_vm.name) + if vm.netvm_vm is not None: + print fmt.format ("netvm", vm.netvm_vm.name) + print fmt.format ("updateable?", vm.is_updateable()) + print fmt.format ("installed by RPM?", vm.installed_by_rpm) + print fmt.format ("dir", vm.dir_path) + print fmt.format ("config", vm.conf_file) + if not vm.is_appvm(): + print fmt.format ("root img", vm.root_img) + if vm.is_appvm(): + print fmt.format ("root img", vm.template_vm.root_img) + print fmt.format ("root COW img", vm.rootcow_img) + + print fmt.format ("private img", vm.private_img) + + +def set_label(vms, vm, args): + if len (args) != 1: + print "Missing label name argument!" + + label = args[0] + if label not in QubesVmLabels: + print "Wrong label name, supported values are the following:" + for l in QubesVmLabels.values(): + print "* {0}".format(l.name) + exit (1) + + vm.label = QubesVmLabels[label] + subprocess.check_call (["ln", "-sf", vm.label.icon_path, vm.icon_path]) + + +def set_netvm(vms, vm, args): + if len (args) != 1: + print "Missing netvm name argument!" + print "Possible values:" + print "1) default" + print "2) none" + print "3) " + return + + netvm = args[0] + if netvm == "none": + netvm_vm = None + vm.uses_default_netvm = False + elif netvm == "default": + netvm_vm = vms.get_default_netvm_vm() + vm.uses_default_netvm = True + else: + netvm_vm = vms.get_vm_by_name (netvm) + if netvm_vm is None: + print "A VM with the name '{0}' does not exist in the system.".format(netvm) + exit(1) + if not netvm_vm.is_netvm(): + print "VM '{0}' is not a NetVM".format(netvm) + exit (1) + vm.uses_default_netvm = False + + vm.netvm_vm = netvm_vm + + +def set_updateable(vms, vm, args): + if vm.is_updateable(): + print "VM '{0}' is already set 'updateable', no action required.".format(vm.name) + return True + + if vm.is_running(): + print "Cannot change 'updateable' attribute of a running VM. Shut it down first." + return False + + if vm.is_appvm(): + # Check if the Template is *non* updateable... + if not vm.template_vm.is_updateable(): + print "VM '{0}': Setting 'updateable' attribute to True.".format(vm.name) + vm.set_updateable() + else: + print "The Template VM ('{0}') is marked as 'updateable' itself!".format(vm.template_vm.name) + print "Cannot make the AppVM updateable too, as this might cause COW-backed storage incoherency." + print "If you want to make this AppVM updateable, you must first make the Template VM nonupdateable." + return False + + if vm.is_templete(): + # Make sure that all the AppVMs are non-updateable... + for appvm in vm.appvms.values(): + if appvm.is_updateable(): + print "At least one of the AppVMs ('{0}') of this Template VM is also marked 'updateable'.".format(appvm.name) + print "Cannot make the Template VM updateable too, as this might cause COW-backed storage incoherency." + print "If you want to make this Template VM updateable, you must first make all its decedent AppVMs nonupdateable." + return False + + + print "VM '{0}': Setting 'updateable' attribute to True.".format(vm.name) + vm.set_updateable() + + return True + +def set_nonupdateable(vms, vm, args): + if not vm.is_updateable(): + print "VM '{0}' is already set 'nonupdateable', no action required.".format(vm.name) + return True + + if vm.is_running(): + print "Cannot change 'updateable' attribute of a running VM. Shut it down first." + return False + + if vm.is_netvm(): + print "Why, on earth, would you want to make a NetVM 'nonupdateable'?" + return False + + + print "VM '{0}': Setting 'updateable' attribute to False.".format(vm.name) + vm.set_nonupdateable() + return True + +properties = { + "updateable": set_updateable, + "nonupdateable": set_nonupdateable, + "label" : set_label, + "netvm" : set_netvm, +} + + +def do_set(vms, vm, property, args): + if property not in properties.keys(): + print "ERROR: Wrong property name: '{0}'".format(property) + return False + + return properties[property](vms, vm, args) + + +def main(): + usage = "usage: %prog -l [options] \n"\ + "usage: %prog -s [options] [...]\n"\ + "List/set various per-VM properties." + + parser = OptionParser (usage) + parser.add_option ("-l", "--list", action="store_true", dest="do_list", default=False) + parser.add_option ("-s", "--set", action="store_true", dest="do_set", default=False) + + (options, args) = parser.parse_args () + if (len (args) < 1): + parser.error ("You must provide at least the vmname!") + + vmname = args[0] + + if options.do_list and options.do_set: + print "You cannot provide -l and -s at the same time!" + exit (1) + + + + if options.do_set: + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + else: + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None or vm.qid not in qvm_collection: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + if options.do_set: + if len (args) < 2: + print "You must specify the property you wish to set..." + print "Available properties:" + for p in properties.keys(): + print "--> '{0}'".format(p) + exit (1) + + property = args[1] + do_set(qvm_collection, vm, property, args[2:]) + qvm_collection.save() + qvm_collection.unlock_db() + + + else: + # do_list + do_list(vm) + +main() diff --git a/dom0/qvm-tools/qvm-remove b/dom0/qvm-tools/qvm-remove new file mode 100755 index 00000000..06e39af5 --- /dev/null +++ b/dom0/qvm-tools/qvm-remove @@ -0,0 +1,101 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from optparse import OptionParser; + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) + parser.add_option ("--just-db", action="store_true", dest="remove_from_db_only", default=False, + help="Remove only from the Qubes Xen DB, do not remove any files") + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify VM name!") + vmname = args[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None or vm.qid not in qvm_collection: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + if vm.is_templete(): + dependent_vms = qvm_collection.get_vms_based_on(vm.qid) + if len(dependent_vms) > 0: + print "The following AppVMs use '{0}' as a template:".format(vmname) + for vm in dependent_vms: + print "{name:<12} (qid={qid})".format(qid=vm.qid, name=vm.name) + print "Please remove those VMs first, or use the --force option." + exit (1) + if qvm_collection.default_template_qid == vm.qid: + qvm_collection.default_template_qid = None + + if vm.is_netvm(): + if qvm_collection.default_netvm_qid == vm.qid: + qvm_collection.default_netvm_qid = None + + + if vm.is_running(): + print "Cannot remove a running VM, stop it first" + exit (1) + + if vm.installed_by_rpm and not options.remove_from_db_only: + if options.verbose: + print "This VM has been installed by RPM, use rpm -e to remove it!" + exit (1) + + try: + if options.verbose: + print "--> Removing from Xen Storage..." + vm.remove_from_xen_storage() + except (IOError, OSError) as err: + print "Warning: {0}".format(err) + # Do not exit, perhaps the VM was not in the Xen store + # so just remove it from Qubes DB + + try: + if vm.installed_by_rpm: + if options.verbose: + print "--> VM installed by RPM, leaving all the files on disk" + else: + if options.verbose: + print "--> Removing all the files on disk..." + #TODO: ask for confirmation, perhaps? + vm.remove_from_disk() + + + + except (IOError, OSError) as err: + print "Warning: {0}".format(err) + # Do not exit, perhaps the VM files were somehow removed + # so just remove it from Qubes DB + + + qvm_collection.pop(vm.qid) + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-run b/dom0/qvm-tools/qvm-run new file mode 100755 index 00000000..76c7c589 --- /dev/null +++ b/dom0/qvm-tools/qvm-run @@ -0,0 +1,227 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser +import subprocess +import socket +import errno +import dbus +import time + +qubes_guid_path = "/usr/bin/qubes_guid" +qubes_clipd_path = "/usr/bin/qclipd" +qubes_qfilexchgd_path= "/usr/bin/qfilexchgd" +notify_object = None + +# how long (in sec) to wait for VMs to shutdown +# before killing them (when used with --wait option) +shutdown_counter_max = 30 + +def tray_notify(str, label, timeout = 3000): + notify_object.Notify("Qubes", 0, label.icon, "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + +def tray_notify_error(str, timeout = 3000): + notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications") + + +def vm_run_cmd(vm, cmd, options): + if options.shutdown: + if options.verbose: + print "Shutting down VM: '{0}'...".format(vm.name) + subprocess.call (["/usr/sbin/xm", "shutdown", vm.name]) + return + + if options.verbose: + print "Running command on VM: '{0}'...".format(vm.name) + + if not vm.is_running(): + if not options.auto: + print "VM '{0}' is not running, please start it first, or use the '--auto' switch" + exit (1) + try: + if options.verbose: + print "Starting the VM '{0}'...".format(vm.name) + 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: + tray_notify_error ("Error while starting the '{0}' VM: {1}".format(vm.name, err)) + exit (1) + except (MemoryError) as err: + print "ERROR: {0}".format(err) + print "Close one or more running VMs and try again." + if options.tray: + 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..." + + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-e", cmd, "-i", vm.label.icon]) + 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) + 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]) + 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) + +def main(): + usage = "usage: %prog [options] [] []" + parser = OptionParser (usage) + parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) + parser.add_option ("-a", "--auto", action="store_true", dest="auto", default=False, + help="Auto start the VM if not running") + parser.add_option ("-u", "--user", action="store", dest="user", default="user", + help="Run command in a VM as a specified user") + parser.add_option ("--tray", action="store_true", dest="tray", default=False, + help="Use tray notifications instead of stdout" ) + + parser.add_option ("--all", action="store_true", dest="run_on_all_running", default=False, + help="Run command on all currently running VMs") + + parser.add_option ("--exclude", action="append", dest="exclude_list", + help="When --all is used: exclude this VM name (might be repeated)") + + parser.add_option ("--wait", action="store_true", dest="wait_for_shutdown", default=False, + help="Wait for the VM(s) to shutdown") + + parser.add_option ("--shutdown", action="store_true", dest="shutdown", default=False, + help="Do 'xm shutdown' for the VM(s) (can be combined this with --all and --wait)") + + (options, args) = parser.parse_args () + + + if options.run_on_all_running: + if len(args) < 1 and not options.shutdown: + parser.error ("You must provide a command to execute on all the VMs.") + if len(args) > 1 or (options.shutdown and len(args) > 0): + parser.error ("To many arguments...") + cmdstr = args[0] if not options.shutdown else None + else: + if len (args) < 1 and options.shutdown: + parser.error ("You must specify the VM name to shutdown.") + if len (args) < 2 and not options.shutdown: + parser.error ("You must specify the VM name and the command to execute in the VM.") + if len (args) > 2 or (options.shutdown and len(args) > 1): + parser.error ("To many arguments...") + vmname = args[0] + cmdstr = args[1] if not options.shutdown else None + + if options.tray: + global notify_object + notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_reading() + qvm_collection.load() + qvm_collection.unlock_db() + + vms_list = [] + if options.run_on_all_running: + all_vms = [vm for vm in qvm_collection.values()] + for vm in all_vms: + if options.exclude_list is not None and vm.name in options.exclude_list: + continue + if vm.qid == 0: + continue + if vm.is_running(): + vms_list.append (vm) + else: + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None: + print "A VM with the name '{0}' does not exist in the system!".format(vmname) + exit(1) + vms_list.append(vm) + + if options.shutdown: + cmd = None + else: + cmd = "{user}:{cmd}".format(user=options.user, cmd=cmdstr) + + for vm in vms_list: + vm_run_cmd(vm, cmd, options) + + + if options.wait_for_shutdown: + if options.verbose: + print "Waiting for the VM(s) to shutdown..." + shutdown_counter = 0 + + while len (vms_list): + if options.verbose: + print "Waiting for VMs: ", [vm.name for vm in vms_list] + for vm in vms_list: + if not vm.is_running(): + vms_list.remove (vm) + if shutdown_counter > shutdown_counter_max: + # kill the VM + if options.verbose: + print "Killing the (apparently hanging) VM '{0}'...".format(vm.name) + vm.force_shutdown() + #vms_list.remove(vm) + + shutdown_counter += 1 + time.sleep (1) + exit (0) # there is no point in executing the other daemons in the case of --wait + + retcode = subprocess.call([qubes_clipd_path]) + if retcode != 0: + print "ERROR: Cannot start qclipd!" + if options.tray: + tray_notify ("ERROR: Cannot start the Qubes Clipboard Notifier!") + + retcode = subprocess.call([qubes_qfilexchgd_path]) + if retcode != 0: + print "ERROR: Cannot start qfilexchgd!" + if options.tray: + tray_notify ("ERROR: Cannot start the Qubes Inter-VM File Exchange Daemon!") + + +main() diff --git a/dom0/qvm-tools/qvm-set-default-netvm b/dom0/qvm-tools/qvm-set-default-netvm new file mode 100755 index 00000000..672ff83a --- /dev/null +++ b/dom0/qvm-tools/qvm-set-default-netvm @@ -0,0 +1,50 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from optparse import OptionParser; + +def main(): + usage = "usage: %prog " + parser = OptionParser (usage) + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("Missing argument!") + vmname = args[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None or vm.qid not in qvm_collection: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + if not vm.is_netvm(): + print "VM '{0}' is not a NetVM".format(vmname) + exit (1) + + qvm_collection.set_default_netvm_vm(vm) + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-set-default-template b/dom0/qvm-tools/qvm-set-default-template new file mode 100755 index 00000000..429d34ad --- /dev/null +++ b/dom0/qvm-tools/qvm-set-default-template @@ -0,0 +1,50 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from optparse import OptionParser; + +def main(): + usage = "usage: %prog " + parser = OptionParser (usage) + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("Missing argument!") + vmname = args[0] + + qvm_collection = QubesVmCollection() + qvm_collection.lock_db_for_writing() + qvm_collection.load() + vm = qvm_collection.get_vm_by_name(vmname) + if vm is None or vm.qid not in qvm_collection: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + if not vm.is_templete(): + print "VM '{0}' is not a TemplateVM".format(vmname) + exit (1) + + qvm_collection.set_default_template_vm(vm) + qvm_collection.save() + qvm_collection.unlock_db() + +main() diff --git a/dom0/qvm-tools/qvm-start b/dom0/qvm-tools/qvm-start new file mode 100755 index 00000000..0044438a --- /dev/null +++ b/dom0/qvm-tools/qvm-start @@ -0,0 +1,72 @@ +#!/usr/bin/python2.6 +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from optparse import OptionParser +import subprocess + +qubes_guid_path = "/usr/bin/qubes_guid" + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser (usage) + 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 ("--console", action="store_true", dest="debug_console", default=False, + help="Attach debugging console to the newly started VM") + + (options, args) = parser.parse_args () + if (len (args) != 1): + parser.error ("You must specify VM name!") + vmname = args[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) + if vm is None: + print "A VM with the name '{0}' does not exist in the system.".format(vmname) + exit(1) + + try: + vm.verify_files() + xid = vm.start(debug_console=options.debug_console, verbose=options.verbose) + except (IOError, OSError, QubesException) as err: + print "ERROR: {0}".format(err) + exit (1) + + if options.noguid: + exit (0) + if options.verbose: + print "--> Starting Qubes GUId..." + + retcode = subprocess.call ([qubes_guid_path, "-d", str(xid), "-c", vm.label.color, "-i", vm.label.icon]) + if (retcode != 0) : + print "ERROR: Cannot start qubes_guid!" + exit (1) + + +main() diff --git a/netvm/fstab b/netvm/fstab new file mode 100644 index 00000000..403b0d5c --- /dev/null +++ b/netvm/fstab @@ -0,0 +1,14 @@ + +# +# /etc/fstab +# Created by anaconda on Thu Dec 3 11:26:49 2009 +# +# Accessible filesystems, by reference, are maintained under '/dev/disk' +# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info +# +/dev/mapper/dmroot / ext4 defaults,noatime 1 1 +/dev/mapper/dmswap swap swap defaults 0 0 +tmpfs /dev/shm tmpfs defaults 0 0 +devpts /dev/pts devpts gid=5,mode=620 0 0 +sysfs /sys sysfs defaults 0 0 +proc /proc proc defaults 0 0 diff --git a/netvm/iptables b/netvm/iptables new file mode 100644 index 00000000..67d2d808 --- /dev/null +++ b/netvm/iptables @@ -0,0 +1,17 @@ +# Generated by iptables-save v1.4.5 on Thu Apr 1 10:55:18 2010 +*nat +:PREROUTING ACCEPT [3:696] +:POSTROUTING ACCEPT [1:67] +:OUTPUT ACCEPT [1:67] +-A POSTROUTING -s 10.1.0.0/16 -j MASQUERADE +COMMIT +# Completed on Thu Apr 1 10:55:18 2010 +# Generated by iptables-save v1.4.5 on Thu Apr 1 10:55:18 2010 +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -i br0 -p udp -m udp --dport 68 -j DROP +-A INPUT -i vif+ -p udp -m udp --dport 68 -j DROP +COMMIT +# Completed on Thu Apr 1 10:55:18 2010 diff --git a/netvm/qubes_core b/netvm/qubes_core new file mode 100755 index 00000000..776fa7a9 --- /dev/null +++ b/netvm/qubes_core @@ -0,0 +1,55 @@ +#!/bin/sh +# +# chkconfig: 345 90 90 +# description: Executes Qubes core scripts at VM boot +# +# Source function library. +. /etc/rc.d/init.d/functions + +start() +{ + echo -n $"Executing Qubes Core scripts NetVM:" + + if ! [ -x /usr/bin/xenstore-read ] ; then + echo "ERROR: /usr/bin/xenstore-read not found!" + exit 1 + fi + + name=$(/usr/bin/xenstore-read name) + hostname $name + + # Setup gateway for all the VMs this netVM is serviceing... + brctl addbr br0 + gateway=$(/usr/bin/xenstore-read qubes_netvm_gateway) + netmask=$(/usr/bin/xenstore-read qubes_netvm_netmask) + network=$(/usr/bin/xenstore-read qubes_netvm_network) + ifconfig br0 $gateway netmask $netmask up + echo "1" > /proc/sys/net/ipv4/ip_forward + dnsmasq --listen-address $gateway --bind-interfaces +#now done by iptables rc script +# iptables -t nat -A POSTROUTING -s $network/$netmask -j MASQUERADE + + success + echo "" + return 0 +} + +stop() +{ + return 0 +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + *) + echo $"Usage: $0 {start|stop}" + exit 3 + ;; +esac + +exit $RETVAL diff --git a/rpm_spec/core-appvm.spec b/rpm_spec/core-appvm.spec new file mode 100644 index 00000000..d88ea3ca --- /dev/null +++ b/rpm_spec/core-appvm.spec @@ -0,0 +1,162 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +%{!?version: %define version %(cat version_vm)} + +Name: qubes-core-appvm +Version: %{version} +Release: 1 +Summary: The Qubes core files for AppVM + +Group: Qubes +Vendor: Invisible Things Lab +License: GPL +URL: http://www.qubes-os.org +Requires: /usr/bin/xenstore-read +Provides: qubes-core-vm + +%define _builddir %(pwd)/appvm + +%define kde_service_dir /usr/share/kde4/services/ServiceMenus + +%description +The Qubes core files for installation inside a Qubes AppVM. + +%pre + +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes +[ -e $RPM_BUILD_ROOT/etc/fstab ] && mv $RPM_BUILD_ROOT/etc/fstab $RPM_BUILD_ROOT/var/lib/qubes/fstab.orig + +%build +make clean all + +%install + +mkdir -p $RPM_BUILD_ROOT/etc +cp fstab $RPM_BUILD_ROOT/etc/fstab +mkdir -p $RPM_BUILD_ROOT/etc/init.d +cp qubes_core $RPM_BUILD_ROOT/etc/init.d/ +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes +mkdir -p $RPM_BUILD_ROOT/usr/bin +cp qubes_add_pendrive_script qubes_penctl qvm-copy-to-vm qvm-copy-to-vm.kde $RPM_BUILD_ROOT/usr/bin +mkdir -p $RPM_BUILD_ROOT/%{kde_service_dir} +cp qvm-copy.desktop $RPM_BUILD_ROOT/%{kde_service_dir} +mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d +cp qubes.rules $RPM_BUILD_ROOT/etc/udev/rules.d +mkdir -p $RPM_BUILD_ROOT/etc/sysconfig +cp iptables $RPM_BUILD_ROOT/etc/sysconfig/ +mkdir -p $RPM_BUILD_ROOT/mnt/incoming +mkdir -p $RPM_BUILD_ROOT/mnt/outgoing + +%post + +if [ "$1" != 1 ] ; then +# do this whole %post thing only when updating for the first time... +exit 0 +fi + +echo "--> Disabling SELinux..." +sed -e s/^SELINUX=.*$/SELINUX=disabled/ /etc/selinux/config.processed +mv /etc/selinux/config.processed /etc/selinux/config +setenforce 0 + +echo "--> Turning off unnecessary services..." +# FIXME: perhaps there is more elegant way to do this? +for f in /etc/init.d/* +do + srv=`basename $f` + [ $srv = 'functions' ] && continue + [ $srv = 'killall' ] && continue + [ $srv = 'halt' ] && continue + chkconfig $srv off +done + +echo "--> Enabling essential services..." +chkconfig rsyslog on +chkconfig haldaemon on +chkconfig messagebus on +chkconfig cups on +chkconfig iptables on +chkconfig --add qubes_core || echo "WARNING: Cannot add service qubes_core!" +chkconfig qubes_core on || echo "WARNING: Cannot enable service qubes_core!" + + +sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab + +# Remove most of the udev scripts to speed up the VM boot time +# Just leave the xen* scripts, that are needed if this VM was +# ever used as a net backend (e.g. as a VPN domain in the future) +echo "--> Removing unnecessary udev scripts..." +mkdir -p /var/lib/qubes/removed-udev-scripts +for f in /etc/udev/rules.d/* +do + if [ $(basename $f) == "xen-backend.rules" ] ; then + continue + fi + + if [ $(basename $f) == "xend.rules" ] ; then + continue + fi + + if [ $(basename $f) == "qubes.rules" ] ; then + continue + fi + + if [ $(basename $f) == "90-hal.rules" ] ; then + continue + fi + + + mv $f /var/lib/qubes/removed-udev-scripts/ +done + +mkdir -p /rw +#rm -f /etc/mtab +echo "--> Removing HWADDR setting from /etc/sysconfig/network-scripts/ifcfg-eth0" +mv /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.orig +grep -v HWADDR /etc/sysconfig/network-scripts/ifcfg-eth0.orig > /etc/sysconfig/network-scripts/ifcfg-eth0 + +%preun +if [ "$1" = 0 ] ; then + # no more packages left + chkconfig qubes_core off + mv /var/lib/qubes/fstab.orig /etc/fstab + mv /var/lib/qubes/removed-udev-scripts/* /etc/udev/rules.d/ +fi + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/etc/fstab +/etc/init.d/qubes_core +/usr/bin/qvm-copy-to-vm +/usr/bin/qvm-copy-to-vm.kde +%{kde_service_dir}/qvm-copy.desktop +%attr(4755,root,root) /usr/bin/qubes_penctl +/usr/bin/qubes_add_pendrive_script +/etc/udev/rules.d/qubes.rules +/etc/sysconfig/iptables +%dir /var/lib/qubes +%dir /mnt/incoming +%dir /mnt/outgoing diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec new file mode 100644 index 00000000..d61a0002 --- /dev/null +++ b/rpm_spec/core-dom0.spec @@ -0,0 +1,156 @@ +# +# This is the SPEC file for creating binary RPMs for the Dom0. +# +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} + +%{!?version: %define version %(cat version_dom0)} + +Name: qubes-core-dom0 +Version: %{version} +Release: 1 +Summary: The Qubes core files (Dom0-side) + +Group: Qubes +Vendor: Invisible Things Lab +License: GPL +URL: http://www.qubes-os.org +Requires: python, xen-runtime, pciutils, python-inotify, python-daemon, kernel-qubes-dom0 + +%define _builddir %(pwd)/dom0 + +%description +The Qubes core files for installation on Dom0. + +%install + +mkdir -p $RPM_BUILD_ROOT/etc/init.d +cp init.d/qubes_core $RPM_BUILD_ROOT/etc/init.d/ +cp init.d/qubes_netvm $RPM_BUILD_ROOT/etc/init.d/ + +mkdir -p $RPM_BUILD_ROOT/usr/bin/ +cp qvm-tools/qvm-* $RPM_BUILD_ROOT/usr/bin +cp clipboard_notifier/qclipd $RPM_BUILD_ROOT/usr/bin +cp pendrive_swapper/qfilexchgd $RPM_BUILD_ROOT/usr/bin + +mkdir -p $RPM_BUILD_ROOT%{python_sitearch}/qubes +cp qvm-core/qubes.py $RPM_BUILD_ROOT%{python_sitearch}/qubes +cp qvm-core/__init__.py $RPM_BUILD_ROOT%{python_sitearch}/qubes + +mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/patch_appvm_initramfs.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/unbind_pci_device.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/unbind_all_network_devices $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/convert_apptemplate2vm.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/convert_dirtemplate2vm.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/create_apps_for_appvm.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/remove_appvm_appmenus.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp pendrive_swapper/qubes_pencmd $RPM_BUILD_ROOT/usr/lib/qubes + +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/vm-templates +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/appvms + +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes/backup + +mkdir -p $RPM_BUILD_ROOT/usr/share/qubes/icons +cp icons/*.png $RPM_BUILD_ROOT/usr/share/qubes/icons + + +%post +if [ "$1" != 1 ] ; then +# do this whole %post thing only when updating for the first time... +exit 0 +fi + +#echo "Enabling essential services..." +chkconfig haldaemon on +chkconfig messagebus on +chkconfig xenstored on +chkconfig xend on +chkconfig xenconsoled on + +chkconfig --add qubes_core || echo "WARNING: Cannot add service qubes_core!" +chkconfig --add qubes_netvm || echo "WARNING: Cannot add service qubes_netvm!" + +chkconfig qubes_core on || echo "WARNING: Cannot enable service qubes_core!" +chkconfig qubes_netvm on || echo "WARNING: Cannot enable service qubes_netvm!" + +if ! [ -e /var/lib/qubes/qubes.xml ]; then +# echo "Initializing Qubes DB..." + umask 007; sg qubes -c qvm-init-storage +fi +for i in /usr/share/qubes/icons/*.png ; do + xdg-icon-resource install --novendor --size 48 $i +done + +%clean +rm -rf $RPM_BUILD_ROOT + +%pre +if ! grep -q ^qubes: /etc/group ; then + groupadd qubes +fi + +%preun +if [ "$1" = 0 ] ; then + for i in /usr/share/qubes/icons/*.png ; do + xdg-icon-resource uninstall --novendor --size 48 $i + done +fi + +%postun +if [ "$1" = 0 ] ; then + # no more packages left + chgrp root /etc/xen + chmod 700 /etc/xen + groupdel qubes +fi + +%files +%defattr(-,root,root,-) +/etc/init.d/qubes_core +/etc/init.d/qubes_netvm +/usr/bin/qvm-* +/usr/bin/qclipd +/usr/bin/qfilexchgd +%{python_sitearch}/qubes/qubes.py +%{python_sitearch}/qubes/qubes.pyc +%{python_sitearch}/qubes/qubes.pyo +%{python_sitearch}/qubes/__init__.py +%{python_sitearch}/qubes/__init__.pyc +%{python_sitearch}/qubes/__init__.pyo +/usr/lib/qubes/patch_appvm_initramfs.sh +/usr/lib/qubes/unbind_pci_device.sh +/usr/lib/qubes/unbind_all_network_devices +/usr/lib/qubes/convert_apptemplate2vm.sh +/usr/lib/qubes/convert_dirtemplate2vm.sh +/usr/lib/qubes/create_apps_for_appvm.sh +/usr/lib/qubes/remove_appvm_appmenus.sh +/usr/lib/qubes/qubes_pencmd +%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 +%attr(770,root,qubes) %dir /var/lib/qubes/backup +%dir /usr/share/qubes/icons/*.png diff --git a/rpm_spec/core-netvm.spec b/rpm_spec/core-netvm.spec new file mode 100644 index 00000000..39a0cdfb --- /dev/null +++ b/rpm_spec/core-netvm.spec @@ -0,0 +1,142 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# Copyright (C) 2010 Rafal Wojtczuk +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +%{!?version: %define version %(cat version_vm)} + +Name: qubes-core-netvm +Version: %{version} +Release: 1 +Summary: The Qubes core files for NetVM + +Group: Qubes +Vendor: Invisible Things Lab +License: GPL +URL: http://www.qubes-os.org +Requires: /usr/bin/xenstore-read +Provides: qubes-core-vm + +%define _builddir %(pwd)/netvm + +%description +The Qubes core files for installation inside a Qubes NetVM. + +%pre + +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes +[ -e $RPM_BUILD_ROOT/etc/fstab ] && mv $RPM_BUILD_ROOT/etc/fstab $RPM_BUILD_ROOT/var/lib/qubes/fstab.orig + +%build + +%install + +mkdir -p $RPM_BUILD_ROOT/etc/sysconfig +cp iptables $RPM_BUILD_ROOT/etc/sysconfig +mkdir -p $RPM_BUILD_ROOT/etc +cp fstab $RPM_BUILD_ROOT/etc/fstab +mkdir -p $RPM_BUILD_ROOT/etc/init.d +cp qubes_core $RPM_BUILD_ROOT/etc/init.d/ +mkdir -p $RPM_BUILD_ROOT/var/lib/qubes + +%post + +if [ "$1" != 1 ] ; then +# do this whole %post thing only when updating for the first time... +exit 0 +fi + +echo "--> Disabling SELinux..." +sed -e s/^SELINUX=.*$/SELINUX=disabled/ /etc/selinux/config.processed +mv /etc/selinux/config.processed /etc/selinux/config +setenforce 0 + +echo "--> Turning off unnecessary services..." +# FIXME: perhaps there is more elegant way to do this? +for f in /etc/init.d/* +do + srv=`basename $f` + [ $srv = 'functions' ] && continue + [ $srv = 'killall' ] && continue + [ $srv = 'halt' ] && continue + chkconfig $srv off +done + +echo "--> Enabling essential services..." +chkconfig iptables on +chkconfig rsyslog on +chkconfig haldaemon on +chkconfig messagebus on +chkconfig NetworkManager on +chkconfig --add qubes_core || echo "WARNING: Cannot add service qubes_core!" +chkconfig qubes_core on || echo "WARNING: Cannot enable service qubes_core!" + + +sed -i s/^id:.:initdefault:/id:3:initdefault:/ /etc/inittab + +# Remove most of the udev scripts to speed up the VM boot time +# Just leave the xen* scripts, that are needed if this VM was +# ever used as a net backend (e.g. as a VPN domain in the future) +echo "--> Removing unnecessary udev scripts..." +mkdir -p /var/lib/qubes/removed-udev-scripts +for f in /etc/udev/rules.d/* +do + if [ $(basename $f) == "xen-backend.rules" ] ; then + continue + fi + + if [ $(basename $f) == "xend.rules" ] ; then + continue + fi + + if [ $(basename $f) == "qubes.rules" ] ; then + continue + fi + + if [ $(basename $f) == "90-hal.rules" ] ; then + continue + fi + + + mv $f /var/lib/qubes/removed-udev-scripts/ +done + +#rm -f /etc/mtab +#echo "--> Removing HWADDR setting from /etc/sysconfig/network-scripts/ifcfg-eth0" +#mv /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.orig +#grep -v HWADDR /etc/sysconfig/network-scripts/ifcfg-eth0.orig > /etc/sysconfig/network-scripts/ifcfg-eth0 + +%preun +if [ "$1" = 0 ] ; then + # no more packages left + chkconfig qubes_core off + mv /var/lib/qubes/fstab.orig /etc/fstab + mv /var/lib/qubes/removed-udev-scripts/* /etc/udev/rules.d/ +fi + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +/etc/fstab +/etc/sysconfig/iptables +/etc/init.d/qubes_core +%dir /var/lib/qubes diff --git a/rpm_spec/dom0-cleanup.spec b/rpm_spec/dom0-cleanup.spec new file mode 100644 index 00000000..3f6dee1b --- /dev/null +++ b/rpm_spec/dom0-cleanup.spec @@ -0,0 +1,78 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2010 Joanna Rutkowska +# +# 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. +# +# + +Name: qubes-dom0-cleanup +Version: 0.2.2 +Release: 1 +Summary: Additional tools that cleans up some unnecessary stuff in Qubes's Dom0 + +Group: Qubes +Vendor: Invisible Things Lab +License: GPL +URL: http://www.qubes-os.org +Requires: qubes-core-dom0 + +%define _builddir %(pwd)/dom0 + +%description +Additional tools that cleans up some unnecessary stuff in Qubes's Dom0 + +%install + +mkdir -p $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/check_and_remove_appmenu.sh $RPM_BUILD_ROOT/usr/lib/qubes +cp aux-tools/remove_dom0_appmenus.sh $RPM_BUILD_ROOT/usr/lib/qubes + +%post +echo "--> Turning off unnecessary services..." +# FIXME: perhaps there is more elegant way to do this? +for f in /etc/init.d/* +do + srv=`basename $f` + [ $srv = 'functions' ] && continue + [ $srv = 'killall' ] && continue + [ $srv = 'halt' ] && continue + chkconfig $srv off +done + +#echo "--> Enabling essential services..." +chkconfig abrtd on +chkconfig haldaemon on +chkconfig messagebus on +chkconfig xenstored on +chkconfig xend on +chkconfig xenconsoled on +chkconfig qubes_core on || echo "WARNING: Cannot enable service qubes_core!" +chkconfig qubes_netvm on || echo "WARNING: Cannot enable service qubes_core!" + +/usr/lib/qubes/remove_dom0_appmenus.sh + +%clean +rm -rf $RPM_BUILD_ROOT + +%postun + +mv /var/lib/qubes/backup/removed-apps/* /usr/share/applications +xdg-desktop-menu forceupdate + +%files +/usr/lib/qubes/check_and_remove_appmenu.sh +/usr/lib/qubes/remove_dom0_appmenus.sh diff --git a/version_dom0 b/version_dom0 new file mode 100644 index 00000000..3eefcb9d --- /dev/null +++ b/version_dom0 @@ -0,0 +1 @@ +1.0.0 diff --git a/version_vm b/version_vm new file mode 100644 index 00000000..3eefcb9d --- /dev/null +++ b/version_vm @@ -0,0 +1 @@ +1.0.0