qfilexchgd 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. #!/usr/bin/python2.6
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. #
  22. import xen.lowlevel.xs
  23. import os
  24. import os.path
  25. import sys
  26. import subprocess
  27. import daemon
  28. import time
  29. import dbus
  30. from qubes.qubes import QubesVmCollection
  31. from qubes.qubes import QubesException
  32. from qubes.qubes import QubesDaemonPidfile
  33. from qubes.qmemman_client import QMemmanClient
  34. filename_seq = 50
  35. pen_cmd = '/usr/lib/qubes/qubes_pencmd'
  36. disposable_domains_dict = {}
  37. current_savefile = '/var/run/qubes/current_savefile'
  38. notify_object = None
  39. def get_next_filename_seq():
  40. global filename_seq
  41. filename_seq = filename_seq + 1
  42. return str(filename_seq)
  43. def logproc(msg):
  44. f = file('/var/log/qubes/qfileexchgd', 'a')
  45. f.write(msg+'\n')
  46. f.close()
  47. def get_req_node(domain_id):
  48. return '/local/domain/'+domain_id+'/device/qpen'
  49. def get_name_node(domain_id):
  50. return '/local/domain/'+domain_id+'/name'
  51. def only_in_first_list(l1, l2):
  52. ret=[]
  53. for i in l1:
  54. if not i in l2:
  55. ret.append(i)
  56. return ret
  57. class WatchType:
  58. def __init__(self, fn, param):
  59. self.fn = fn
  60. self.param = param
  61. class DomainState:
  62. def __init__(self, domain, dict):
  63. self.rcv_state = 'idle'
  64. self.send_state = 'idle'
  65. self.domain_id = domain
  66. self.domdict = dict
  67. self.send_seq = None
  68. self.rcv_seq = None
  69. self.waiting_sender = None
  70. self.allowed_dest = None
  71. self.allowed_seq = None
  72. def killme(self):
  73. if not os.path.isfile('/etc/debug-dvm'):
  74. subprocess.call(['/usr/sbin/xm', 'destroy', self.domain_id])
  75. def handle_request(self, request):
  76. req_ok = False
  77. if request is None:
  78. return
  79. tmp = request.split()
  80. rq = tmp[0]
  81. if len(tmp) > 1:
  82. vmname = tmp[1]
  83. else:
  84. vmname = None
  85. if len(tmp) > 2:
  86. transaction_seq = tmp[2]
  87. else:
  88. transaction_seq = '0'
  89. if rq == 'killme':
  90. self.killme()
  91. req_ok = True
  92. if rq == 'new' and self.send_state == 'idle':
  93. self.send_seq = get_next_filename_seq()
  94. retcode = subprocess.call([pen_cmd, 'new', self.domain_id, self.send_seq])
  95. logproc( 'Give domain ' + self.domain_id + ' a clean pendrive, retcode= ' + str(retcode))
  96. if retcode == 0:
  97. self.send_state = 'has_clean_pendrive'
  98. req_ok = True
  99. if rq == 'send' and self.send_state == 'has_clean_pendrive' and vmname is not None:
  100. logproc( 'send from ' + self.domain_id + ' to ' + vmname)
  101. if self.handle_transfer(vmname, transaction_seq):
  102. self.send_state = 'idle'
  103. req_ok = True;
  104. if rq == 'umount' and self.rcv_state == 'has_loaded_pendrive':
  105. retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq])
  106. if retcode == 0:
  107. self.rcv_state = 'idle'
  108. self.rcv_seq = None
  109. logproc( 'set state of ' + self.domain_id + ' loaded->idle retcode=' + str(retcode))
  110. req_ok = True
  111. if rq == 'umount' and self.rcv_state == 'waits_to_umount':
  112. req_ok = True
  113. retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq])
  114. if retcode != 0:
  115. return
  116. assert(self.waiting_sender != None)
  117. self.rcv_state = 'idle'
  118. self.rcv_seq = None
  119. tmp = self.waiting_sender
  120. self.waiting_sender = None
  121. if tmp.send_state == 'has_clean_pendrive':
  122. if tmp.handle_transfer(self.name, tmp.delayed_transaction_seq):
  123. tmp.send_state = 'idle'
  124. if not req_ok:
  125. logproc( 'request ' + request + ' not served due to nonmatching state')
  126. def ask_to_umount(self, vmname):
  127. q = 'VM ' + vmname + ' has already an incoming pendrive, and thus '
  128. q+= 'cannot accept another one. If you intend to unmount its current '
  129. q+= 'pendrive and retry this transfer, press Yes. '
  130. q+= 'Otherwise press No to fail this transfer.'
  131. retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'Some additional action required'])
  132. if retcode == 0:
  133. return True
  134. else:
  135. return False
  136. def handle_transfer_regular(self, vmname, transaction_seq):
  137. qvm_collection = QubesVmCollection()
  138. qvm_collection.lock_db_for_reading()
  139. qvm_collection.load()
  140. qvm_collection.unlock_db()
  141. vm = qvm_collection.get_vm_by_name(vmname)
  142. if vm is None:
  143. logproc( 'Domain ' + vmname + ' does not exist ?')
  144. return False
  145. if not vm.is_running():
  146. logproc( 'Domain ' + vmname + ' is not running ?')
  147. return False
  148. target=self.domdict[str(vm.get_xid())]
  149. if target.rcv_state != 'idle':
  150. if self.ask_to_umount(vmname):
  151. target.rcv_state='waits_to_umount'
  152. target.waiting_sender=self
  153. self.delayed_transaction_seq=transaction_seq
  154. logproc( 'target domain ' + target.domain_id + ' is not idle, now ' + target.rcv_state)
  155. return False
  156. if self.allowed_seq is not None:
  157. if self.allowed_seq != transaction_seq or self.allowed_dest != target.name:
  158. logproc('sender ' + self.name + ' receiver ' + target.name + ' : allowed attributes mismatch, denied')
  159. return False
  160. else:
  161. transaction_seq = '0'
  162. retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', 'Do you authorize pendrive transfer from ' + self.name + ' to ' + vmname + '?' , '--title', 'Security confirmation'])
  163. logproc('handle_transfer: kdialog retcode=' + str(retcode))
  164. if retcode != 0:
  165. return False
  166. target.rcv_state='has_loaded_pendrive'
  167. retcode = subprocess.call([pen_cmd, 'send', self.domain_id, target.domain_id, self.send_seq, transaction_seq])
  168. target.rcv_seq = self.send_seq
  169. self.send_seq = None
  170. logproc( 'set state of ' + target.domain_id + ' to has_loaded_pendrive, retcode=' + str(retcode))
  171. if self.allowed_seq is not None:
  172. self.killme()
  173. return True
  174. def handle_transfer_disposable(self, transaction_seq):
  175. qmemman_client = QMemmanClient()
  176. if not qmemman_client.request_memory(400*1024*1024):
  177. qmemman_client.close()
  178. errmsg = 'Not enough memory to create DVM. '
  179. errmsg +='Terminate some appVM and retry.'
  180. subprocess.call(['/usr/bin/kdialog', '--sorry', errmsg])
  181. return False
  182. qvm_collection = QubesVmCollection()
  183. qvm_collection.lock_db_for_writing()
  184. qvm_collection.load()
  185. vm = qvm_collection.get_vm_by_name(self.name)
  186. if vm is None:
  187. logproc( 'Domain ' + vmname + ' does not exist ?')
  188. qvm_collection.unlock_db()
  189. qmemman_client.close()
  190. return False
  191. retcode = subprocess.call(['/usr/lib/qubes/qubes_restore',
  192. current_savefile,
  193. '-c', vm.label.color,
  194. '-i', vm.label.icon,
  195. '-l', str(vm.label.index)])
  196. qmemman_client.close()
  197. if retcode != 0:
  198. subprocess.call(['/usr/bin/kdialog', '--sorry', 'DisposableVM creation failed, see qubes_restore.log'])
  199. qvm_collection.unlock_db()
  200. return False
  201. f = open('/var/run/qubes/dispVM_xid', 'r');
  202. disp_xid = f.readline().rstrip('\n')
  203. disp_name = f.readline().rstrip('\n')
  204. disptempl = f.readline().rstrip('\n')
  205. f.close()
  206. vm_disptempl = qvm_collection.get_vm_by_name(disptempl);
  207. if vm_disptempl is None:
  208. logproc( 'Domain ' + disptempl + ' does not exist ?')
  209. qvm_collection.unlock_db()
  210. return False
  211. qvm_collection.add_new_disposablevm(disp_name, vm_disptempl.template_vm, label=vm.label)
  212. qvm_collection.save()
  213. qvm_collection.unlock_db()
  214. dispdom = DomainState(disp_xid, self.domdict)
  215. disposable_domains_dict[disp_xid] = dispdom
  216. retcode = subprocess.call([pen_cmd, 'send', self.domain_id, disp_xid, self.send_seq, transaction_seq])
  217. dispdom.rcv_seq = self.send_seq
  218. dispdom.rcv_state = 'has_loaded_pendrive'
  219. dispdom.allowed_dest = self.name
  220. dispdom.allowed_seq = transaction_seq
  221. self.send_seq = None
  222. logproc( 'sent pendrive to disposable xid ' + disp_xid)
  223. return True
  224. def dvm_setup_ok(self):
  225. dvmdata_dir = '/var/lib/qubes/dvmdata/'
  226. if not os.path.isfile(current_savefile):
  227. return False
  228. if not os.path.isfile(dvmdata_dir+'default_savefile') or not os.path.isfile(dvmdata_dir+'savefile_root'):
  229. return False
  230. dvm_mtime = os.stat(current_savefile).st_mtime
  231. root_mtime = os.stat(dvmdata_dir+'savefile_root').st_mtime
  232. if dvm_mtime < root_mtime:
  233. return False
  234. return True
  235. def tray_notify(self, str, timeout = 3000):
  236. notify_object.Notify("Qubes", 0, "red", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  237. def tray_notify_error(self, str, timeout = 3000):
  238. notify_object.Notify("Qubes", 0, "dialog-error", "Qubes", str, [], [], timeout, dbus_interface="org.freedesktop.Notifications")
  239. def handle_transfer(self, vmname, transaction_seq):
  240. if vmname != 'disposable':
  241. return self.handle_transfer_regular(vmname, transaction_seq)
  242. if not self.dvm_setup_ok():
  243. self.tray_notify("Updating DisposableVM savefile, please wait")
  244. if os.system("qvm-create-default-dvm --default-template --default-script") != 0:
  245. self.tray_notify_error("DVM savefile creation failed")
  246. return False
  247. return self.handle_transfer_disposable(transaction_seq)
  248. class XS_Watcher:
  249. def __init__(self):
  250. self.handle = xen.lowlevel.xs.xs()
  251. self.handle.watch('/vm', WatchType(XS_Watcher.dom_list_change, None))
  252. self.domdict = {}
  253. def dom_list_change(self, param):
  254. curr = self.handle.ls('', '/local/domain')
  255. if curr == None:
  256. return
  257. for i in only_in_first_list(curr, self.domdict.keys()):
  258. if disposable_domains_dict.has_key(i):
  259. newdom = disposable_domains_dict[i]
  260. else:
  261. newdom = DomainState(i, self.domdict)
  262. newdom.name = ''
  263. self.domdict[i] = newdom
  264. newdom.watch_token = WatchType(XS_Watcher.request, newdom)
  265. newdom.watch_name = WatchType(XS_Watcher.namechange, newdom)
  266. self.handle.watch(get_req_node(i), newdom.watch_token)
  267. self.handle.watch(get_name_node(i), newdom.watch_name)
  268. logproc( 'added domain ' + i)
  269. for i in only_in_first_list(self.domdict.keys(), curr):
  270. if disposable_domains_dict.has_key(i):
  271. self.remove_disposable_from_qdb(self.domdict[i].name)
  272. disposable_domains_dict.pop(i)
  273. self.handle.unwatch(get_req_node(i), self.domdict[i].watch_token)
  274. self.handle.unwatch(get_name_node(i), self.domdict[i].watch_name)
  275. self.domdict.pop(i)
  276. logproc( 'removed domain ' + i)
  277. def request(self, domain_param):
  278. ret = self.handle.read('', get_req_node(domain_param.domain_id))
  279. domain_param.handle_request(ret)
  280. def namechange(self, domain_param):
  281. ret = self.handle.read('', get_name_node(domain_param.domain_id))
  282. if ret!= '' and ret!=None:
  283. domain_param.name = ret
  284. logproc( 'Name for domain xid ' + domain_param.domain_id + ' is ' + ret )
  285. def remove_disposable_from_qdb(self, name):
  286. qvm_collection = QubesVmCollection()
  287. qvm_collection.lock_db_for_writing()
  288. qvm_collection.load()
  289. vm = qvm_collection.get_vm_by_name(name)
  290. if vm is None:
  291. logproc( 'remove_disposable_from_qdb: Domain ' + name + ' does not exist ?')
  292. qvm_collection.unlock_db()
  293. return False
  294. qvm_collection.pop(vm.qid)
  295. qvm_collection.save()
  296. qvm_collection.unlock_db()
  297. def watch_loop(self):
  298. global notify_object
  299. notify_object = dbus.SessionBus().get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
  300. sys.stderr = file('/var/log/qubes/qfileexchgd.errors', 'a')
  301. while True:
  302. result = self.handle.read_watch()
  303. token = result[1]
  304. token.fn(self, token.param)
  305. def main():
  306. lock = QubesDaemonPidfile ("qfileexchgd")
  307. if lock.pidfile_exists():
  308. if lock.pidfile_is_stale():
  309. lock.remove_pidfile()
  310. print "Removed stale pidfile (has the previous daemon instance crashed?)."
  311. else:
  312. exit (0)
  313. context = daemon.DaemonContext(
  314. working_directory = "/var/run/qubes",
  315. pidfile = lock)
  316. with context:
  317. XS_Watcher().watch_loop()
  318. main()