qfilexchgd 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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 sys
  25. import subprocess
  26. import daemon
  27. import time
  28. from qubes.qubes import QubesVmCollection
  29. from qubes.qubes import QubesException
  30. from qubes.qubes import QubesDaemonPidfile
  31. filename_seq = 50
  32. pen_cmd = '/usr/lib/qubes/qubes_pencmd'
  33. def get_next_filename_seq():
  34. global filename_seq
  35. filename_seq = filename_seq + 1
  36. return str(filename_seq)
  37. def logproc(msg):
  38. f = file('/var/log/qubes/qfileexchgd', 'a')
  39. f.write(msg+'\n')
  40. f.close()
  41. def get_req_node(domain_id):
  42. return '/local/domain/'+domain_id+'/device/qpen'
  43. def get_name_node(domain_id):
  44. return '/local/domain/'+domain_id+'/name'
  45. def only_in_first_list(l1, l2):
  46. ret=[]
  47. for i in l1:
  48. if not i in l2:
  49. ret.append(i)
  50. return ret
  51. class WatchType:
  52. def __init__(self, fn, param):
  53. self.fn = fn
  54. self.param = param
  55. class DomainState:
  56. def __init__(self, domain, dict):
  57. self.rcv_state = 'idle'
  58. self.send_state = 'idle'
  59. self.domain_id = domain
  60. self.domdict = dict
  61. self.send_seq = None
  62. self.rcv_seq = None
  63. self.waiting_sender = None
  64. def handle_request(self, request):
  65. req_ok = False
  66. if request is None:
  67. return
  68. tmp = request.split()
  69. rq = tmp[0]
  70. if len(tmp) > 1:
  71. arg = tmp[1]
  72. else:
  73. arg = None
  74. if rq == 'new' and self.send_state == 'idle':
  75. self.send_seq = get_next_filename_seq()
  76. retcode = subprocess.call([pen_cmd, 'new', self.domain_id, self.send_seq])
  77. logproc( 'Give domain ' + self.domain_id + ' a clean pendrive, retcode= ' + str(retcode))
  78. if retcode == 0:
  79. self.send_state = 'has_clean_pendrive'
  80. req_ok = True
  81. if rq == 'send' and self.send_state == 'has_clean_pendrive' and arg is not None:
  82. logproc( 'send from ' + self.domain_id + ' to ' + arg)
  83. if self.handle_transfer(arg):
  84. self.send_state = 'idle'
  85. req_ok = True;
  86. if rq == 'umount' and self.rcv_state == 'has_loaded_pendrive':
  87. retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq])
  88. if retcode == 0:
  89. self.rcv_state = 'idle'
  90. self.rcv_seq = None
  91. logproc( 'set state of ' + self.domain_id + ' loaded->idle retcode=' + str(retcode))
  92. req_ok = True
  93. if rq == 'umount' and self.rcv_state == 'waits_to_umount':
  94. req_ok = True
  95. retcode = subprocess.call([pen_cmd, 'umount', self.domain_id, self.rcv_seq])
  96. if retcode != 0:
  97. return
  98. assert(self.waiting_sender != None)
  99. self.rcv_state = 'idle'
  100. self.rcv_seq = None
  101. tmp = self.waiting_sender
  102. self.waiting_sender = None
  103. if tmp.send_state == 'has_clean_pendrive':
  104. if tmp.handle_transfer(self.name):
  105. tmp.send_state = 'idle'
  106. if not req_ok:
  107. logproc( 'request ' + request + ' not served due to nonmatching state')
  108. def ask_to_umount(self, vmname):
  109. q = 'VM ' + vmname + ' has already an incoming pendrive, and thus '
  110. q+= 'cannot accept another one. If you intend to unmount its current '
  111. q+= 'pendrive and retry this transfer, press Yes. '
  112. q+= 'Otherwise press No to fail this transfer.'
  113. retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', q, '--title', 'Some additional action required'])
  114. if retcode == 0:
  115. return True
  116. else:
  117. return False
  118. def handle_transfer(self, vmname):
  119. qvm_collection = QubesVmCollection()
  120. qvm_collection.lock_db_for_reading()
  121. qvm_collection.load()
  122. qvm_collection.unlock_db()
  123. vm = qvm_collection.get_vm_by_name(vmname)
  124. if vm is None:
  125. logproc( 'Domain ' + vmname + ' does not exist ?')
  126. return False
  127. if not vm.is_running():
  128. logproc( 'Domain ' + vmname + ' is not running ?')
  129. return False
  130. target=self.domdict[str(vm.get_xid())]
  131. if target.rcv_state != 'idle':
  132. if self.ask_to_umount(vmname):
  133. target.rcv_state='waits_to_umount'
  134. target.waiting_sender=self
  135. logproc( 'target domain ' + target.domain_id + ' is not idle, now ' + target.rcv_state)
  136. return False
  137. retcode = subprocess.call(['/usr/bin/kdialog', '--yesno', 'Do you authorize pendrive transfer from ' + self.name + ' to ' + vmname + '?' , '--title', 'Security confirmation'])
  138. logproc('handle_transfer: kdialog retcode=' + str(retcode))
  139. if retcode != 0:
  140. return False
  141. target.rcv_state='has_loaded_pendrive'
  142. retcode = subprocess.call([pen_cmd, 'send', self.domain_id, target.domain_id, self.send_seq])
  143. target.rcv_seq = self.send_seq
  144. self.send_seq = None
  145. logproc( 'set state of ' + target.domain_id + ' to has_loaded_pendrive, retcode=' + str(retcode))
  146. return True
  147. class XS_Watcher:
  148. def __init__(self):
  149. self.handle = xen.lowlevel.xs.xs()
  150. self.handle.watch('/local/domain', WatchType(XS_Watcher.dom_list_change, None))
  151. self.domdict = {}
  152. def dom_list_change(self, param):
  153. curr = self.handle.ls('', '/local/domain')
  154. if curr == None:
  155. return
  156. for i in only_in_first_list(curr, self.domdict.keys()):
  157. newdom = DomainState(i, self.domdict)
  158. newdom.watch_token = WatchType(XS_Watcher.request, newdom)
  159. newdom.watch_name = WatchType(XS_Watcher.namechange, newdom)
  160. self.domdict[i] = newdom
  161. self.handle.watch(get_req_node(i), newdom.watch_token)
  162. self.handle.watch(get_name_node(i), newdom.watch_name)
  163. newdom.name = ''
  164. logproc( 'added domain ' + i)
  165. for i in only_in_first_list(self.domdict.keys(), curr):
  166. self.handle.unwatch(get_req_node(i), self.domdict[i].watch_token)
  167. self.handle.unwatch(get_name_node(i), self.domdict[i].watch_name)
  168. self.domdict.pop(i)
  169. logproc( 'removed domain ' + i)
  170. def request(self, domain_param):
  171. ret = self.handle.read('', get_req_node(domain_param.domain_id))
  172. domain_param.handle_request(ret)
  173. def namechange(self, domain_param):
  174. ret = self.handle.read('', get_name_node(domain_param.domain_id))
  175. if ret!= '' and ret!=None:
  176. domain_param.name = ret
  177. logproc( 'Name for domain xid ' + domain_param.domain_id + ' is ' + ret )
  178. def watch_loop(self):
  179. sys.stderr = file('/var/log/qubes/qfileexchgd.errors', 'a')
  180. while True:
  181. result = self.handle.read_watch()
  182. token = result[1]
  183. token.fn(self, token.param)
  184. def main():
  185. lock = QubesDaemonPidfile ("qfileexchgd")
  186. if lock.pidfile_exists():
  187. if lock.pidfile_is_stale():
  188. lock.remove_pidfile()
  189. print "Removed stale pidfile (has the previous daemon instance crashed?)."
  190. else:
  191. exit (0)
  192. context = daemon.DaemonContext(
  193. working_directory = "/var/run/qubes",
  194. pidfile = lock)
  195. with context:
  196. XS_Watcher().watch_loop()
  197. main()