341 lines
13 KiB
Python
Executable File
341 lines
13 KiB
Python
Executable File
#!/usr/bin/python2.6
|
|
#
|
|
# The Qubes OS Project, http://www.qubes-os.org
|
|
#
|
|
# Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
#
|
|
|
|
import xen.lowlevel.xs
|
|
import os
|
|
import os.path
|
|
import sys
|
|
import subprocess
|
|
import daemon
|
|
import time
|
|
from qubes.qubes import QubesVmCollection
|
|
from qubes.qubes import QubesException
|
|
from qubes.qubes import QubesDaemonPidfile
|
|
from qubes.qmemman_client import QMemmanClient
|
|
|
|
filename_seq = 50
|
|
pen_cmd = '/usr/lib/qubes/qubes_pencmd'
|
|
disposable_domains_dict = {}
|
|
current_savefile = '/var/run/qubes/current_savefile'
|
|
|
|
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
|
|
self.allowed_dest = None
|
|
self.allowed_seq = None
|
|
|
|
def killme(self):
|
|
if not os.path.isfile('/etc/debug-dvm'):
|
|
subprocess.call(['/usr/sbin/xm', 'destroy', self.domain_id])
|
|
|
|
def handle_request(self, request):
|
|
req_ok = False
|
|
if request is None:
|
|
return
|
|
tmp = request.split()
|
|
rq = tmp[0]
|
|
if len(tmp) > 1:
|
|
vmname = tmp[1]
|
|
else:
|
|
vmname = None
|
|
if len(tmp) > 2:
|
|
transaction_seq = tmp[2]
|
|
else:
|
|
transaction_seq = '0'
|
|
if rq == 'killme':
|
|
self.killme()
|
|
req_ok = True
|
|
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 vmname is not None:
|
|
logproc( 'send from ' + self.domain_id + ' to ' + vmname)
|
|
if self.handle_transfer(vmname, transaction_seq):
|
|
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, self.delayed_transaction_seq):
|
|
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_regular(self, vmname, transaction_seq):
|
|
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
|
|
self.delayed_transaction_seq=transaction_seq
|
|
logproc( 'target domain ' + target.domain_id + ' is not idle, now ' + target.rcv_state)
|
|
return False
|
|
if self.allowed_seq is not None:
|
|
if self.allowed_seq != transaction_seq or self.allowed_dest != target.name:
|
|
logproc('sender ' + self.name + ' receiver ' + target.name + ' : allowed attributes mismatch, denied')
|
|
return False
|
|
else:
|
|
transaction_seq = '0'
|
|
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, transaction_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))
|
|
if self.allowed_seq is not None:
|
|
self.killme()
|
|
return True
|
|
|
|
def handle_transfer_disposable(self, transaction_seq):
|
|
|
|
qmemman_client = QMemmanClient()
|
|
if not qmemman_client.request_memory(400*1024*1024):
|
|
qmemman_client.close()
|
|
errmsg = 'Not enough memory to create DVM. '
|
|
errmsg +='Terminate some appVM and retry.'
|
|
subprocess.call(['/usr/bin/kdialog', '--sorry', errmsg])
|
|
return False
|
|
|
|
qvm_collection = QubesVmCollection()
|
|
qvm_collection.lock_db_for_writing()
|
|
qvm_collection.load()
|
|
|
|
vm = qvm_collection.get_vm_by_name(self.name)
|
|
if vm is None:
|
|
logproc( 'Domain ' + vmname + ' does not exist ?')
|
|
qvm_collection.unlock_db()
|
|
qmemman_client.close()
|
|
return False
|
|
retcode = subprocess.call(['/usr/lib/qubes/qubes_restore',
|
|
current_savefile,
|
|
'-c', vm.label.color,
|
|
'-i', vm.label.icon,
|
|
'-l', str(vm.label.index)])
|
|
qmemman_client.close()
|
|
if retcode != 0:
|
|
subprocess.call(['/usr/bin/kdialog', '--sorry', 'DisposableVM creation failed, see qubes_restore.log'])
|
|
qvm_collection.unlock_db()
|
|
return False
|
|
f = open('/var/run/qubes/dispVM_xid', 'r');
|
|
disp_xid = f.readline().rstrip('\n')
|
|
disp_name = f.readline().rstrip('\n')
|
|
disptempl = f.readline().rstrip('\n')
|
|
f.close()
|
|
vm_disptempl = qvm_collection.get_vm_by_name(disptempl);
|
|
if vm_disptempl is None:
|
|
logproc( 'Domain ' + disptempl + ' does not exist ?')
|
|
qvm_collection.unlock_db()
|
|
return False
|
|
qvm_collection.add_new_appvm(disp_name, vm_disptempl.template_vm, label=vm.label)
|
|
qvm_collection.save()
|
|
qvm_collection.unlock_db()
|
|
|
|
dispdom = DomainState(disp_xid, self.domdict)
|
|
disposable_domains_dict[disp_xid] = dispdom
|
|
retcode = subprocess.call([pen_cmd, 'send', self.domain_id, disp_xid, self.send_seq, transaction_seq])
|
|
dispdom.rcv_seq = self.send_seq
|
|
dispdom.rcv_state = 'has_loaded_pendrive'
|
|
dispdom.allowed_dest = self.name
|
|
dispdom.allowed_seq = transaction_seq
|
|
self.send_seq = None
|
|
logproc( 'sent pendrive to disposable xid ' + disp_xid)
|
|
return True
|
|
|
|
def handle_transfer(self, vmname, transaction_seq):
|
|
dvmdata_dir = '/var/lib/qubes/dvmdata/'
|
|
if vmname != 'disposable':
|
|
return self.handle_transfer_regular(vmname, transaction_seq)
|
|
if not os.path.isfile(current_savefile):
|
|
subprocess.call(['/usr/bin/kdialog', '--sorry', 'There is no current savefile defined; run Qubes Manager'])
|
|
return False
|
|
if not os.path.isfile(dvmdata_dir+'default_savefile') or not os.path.isfile(dvmdata_dir+'savefile_root'):
|
|
subprocess.call(['/usr/bin/kdialog', '--sorry', 'Default savefile misconfiguration; run Qubes Manager'])
|
|
return False
|
|
dvm_mtime = os.stat(current_savefile).st_mtime
|
|
root_mtime = os.stat(dvmdata_dir+'savefile_root').st_mtime
|
|
if dvm_mtime < root_mtime:
|
|
subprocess.call(['/usr/bin/kdialog', '--sorry', 'Your current savefile is outdated, as you have updated the template VM. Run Qubes Manager and recreate the savefile'])
|
|
return False
|
|
return self.handle_transfer_disposable(transaction_seq)
|
|
|
|
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()):
|
|
if disposable_domains_dict.has_key(i):
|
|
newdom = disposable_domains_dict[i]
|
|
else:
|
|
newdom = DomainState(i, self.domdict)
|
|
newdom.name = ''
|
|
self.domdict[i] = newdom
|
|
newdom.watch_token = WatchType(XS_Watcher.request, newdom)
|
|
newdom.watch_name = WatchType(XS_Watcher.namechange, newdom)
|
|
self.handle.watch(get_req_node(i), newdom.watch_token)
|
|
self.handle.watch(get_name_node(i), newdom.watch_name)
|
|
logproc( 'added domain ' + i)
|
|
for i in only_in_first_list(self.domdict.keys(), curr):
|
|
if disposable_domains_dict.has_key(i):
|
|
self.remove_disposable_from_qdb(self.domdict[i].name)
|
|
disposable_domains_dict.pop(i)
|
|
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 remove_disposable_from_qdb(self, name):
|
|
qvm_collection = QubesVmCollection()
|
|
qvm_collection.lock_db_for_writing()
|
|
qvm_collection.load()
|
|
|
|
vm = qvm_collection.get_vm_by_name(name)
|
|
if vm is None:
|
|
logproc( 'remove_disposable_from_qdb: Domain ' + vmname + ' does not exist ?')
|
|
qvm_collection.unlock_db()
|
|
return False
|
|
qvm_collection.pop(vm.qid)
|
|
qvm_collection.save()
|
|
qvm_collection.unlock_db()
|
|
|
|
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()
|