app.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # -*- encoding: utf8 -*-
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2017 Marek Marczykowski-Górecki
  6. # <marmarek@invisiblethingslab.com>
  7. #
  8. # This program is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU Lesser General Public License as published by
  10. # the Free Software Foundation; either version 2.1 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with this program; if not, see <http://www.gnu.org/licenses/>.
  20. '''
  21. Main Qubes() class and related classes.
  22. '''
  23. import socket
  24. import subprocess
  25. import qubesmgmt.base
  26. import qubesmgmt.vm
  27. import qubesmgmt.exc
  28. QUBESD_SOCK = '/var/run/qubesd.sock'
  29. BUF_SIZE = 4096
  30. class VMCollection(object):
  31. '''Collection of VMs objects'''
  32. def __init__(self, app):
  33. self.app = app
  34. self._vm_list = None
  35. def clear_cache(self):
  36. '''Clear cached list of VMs'''
  37. self._vm_list = None
  38. def refresh_cache(self, force=False):
  39. '''Refresh cached list of VMs'''
  40. if not force and self._vm_list is not None:
  41. return
  42. vm_list_data = self.app.qubesd_call(
  43. 'dom0',
  44. 'mgmt.vm.List'
  45. )
  46. new_vm_list = {}
  47. # FIXME: this will probably change
  48. for vm_data in vm_list_data.splitlines():
  49. vm_name, props = vm_data.decode('ascii').split(' ', 1)
  50. props = props.split(' ')
  51. new_vm_list[vm_name] = dict(
  52. [vm_prop.split('=', 1) for vm_prop in props])
  53. self._vm_list = new_vm_list
  54. def __getitem__(self, item):
  55. if item not in self:
  56. raise KeyError(item)
  57. return qubesmgmt.vm.QubesVM(
  58. self.app, item, self._vm_list[item]['class'])
  59. def __contains__(self, item):
  60. self.refresh_cache()
  61. return item in self._vm_list
  62. def __iter__(self):
  63. self.refresh_cache()
  64. for vm in self._vm_list:
  65. yield self[vm]
  66. def keys(self):
  67. '''Get list of VM names.'''
  68. self.refresh_cache()
  69. return self._vm_list.keys()
  70. class QubesBase(qubesmgmt.base.PropertyHolder):
  71. '''Main Qubes application'''
  72. #: domains (VMs) collection
  73. domains = None
  74. def __init__(self):
  75. super(QubesBase, self).__init__(self, 'mgmt.global.', 'dom0')
  76. self.domains = VMCollection(self)
  77. class QubesLocal(QubesBase):
  78. '''Application object communicating through local socket.
  79. Used when running in dom0.
  80. '''
  81. def qubesd_call(self, dest, method, arg=None, payload=None):
  82. try:
  83. client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  84. client_socket.connect(QUBESD_SOCK)
  85. except IOError:
  86. # TODO:
  87. raise
  88. # src, method, dest, arg
  89. for call_arg in ('dom0', method, dest, arg):
  90. if call_arg is not None:
  91. client_socket.sendall(call_arg.encode('ascii'))
  92. client_socket.sendall(b'\0')
  93. if payload is not None:
  94. client_socket.sendall(payload)
  95. client_socket.shutdown(socket.SHUT_WR)
  96. return_data = b''.join(iter(lambda: client_socket.recv(BUF_SIZE), b''))
  97. return self._parse_qubesd_response(return_data)
  98. class QubesRemote(QubesBase):
  99. '''Application object communicating through qrexec services.
  100. Used when running in VM.
  101. '''
  102. def qubesd_call(self, dest, method, arg=None, payload=None):
  103. service_name = method
  104. if arg is not None:
  105. service_name += '+' + arg
  106. p = subprocess.Popen(['qrexec-client-vm', dest, service_name],
  107. stdin=subprocess.PIPE, stdout=subprocess.PIPE,
  108. stderr=subprocess.PIPE)
  109. (stdout, stderr) = p.communicate(payload)
  110. if p.returncode != 0:
  111. # TODO: use dedicated exception
  112. raise qubesmgmt.exc.QubesException('Service call error: %s',
  113. stderr.decode())
  114. return self._parse_qubesd_response(stdout)