qubesd.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #!/usr/bin/env python3.6
  2. import asyncio
  3. import functools
  4. import io
  5. import os
  6. import shutil
  7. import signal
  8. import struct
  9. import traceback
  10. import libvirtaio
  11. import qubes
  12. import qubes.api
  13. import qubes.api.admin
  14. import qubes.api.internal
  15. import qubes.api.misc
  16. import qubes.utils
  17. import qubes.vm.qubesvm
  18. QUBESD_SOCK = '/var/run/qubesd.sock'
  19. QUBESD_INTERNAL_SOCK = '/var/run/qubesd.internal.sock'
  20. QUBESD_MISC_SOCK = '/var/run/qubesd.misc.sock'
  21. class QubesDaemonProtocol(asyncio.Protocol):
  22. buffer_size = 65536
  23. header = struct.Struct('Bx')
  24. def __init__(self, handler, *args, app, debug=False, **kwargs):
  25. super().__init__(*args, **kwargs)
  26. self.handler = handler
  27. self.app = app
  28. self.untrusted_buffer = io.BytesIO()
  29. self.len_untrusted_buffer = 0
  30. self.transport = None
  31. self.debug = debug
  32. self.event_sent = False
  33. self.mgmt = None
  34. def connection_made(self, transport):
  35. self.transport = transport
  36. def connection_lost(self, exc):
  37. self.untrusted_buffer.close()
  38. # for cancellable operation, interrupt it, otherwise it will do nothing
  39. if self.mgmt is not None:
  40. self.mgmt.cancel()
  41. self.transport = None
  42. def data_received(self, untrusted_data): # pylint: disable=arguments-differ
  43. if self.len_untrusted_buffer + len(untrusted_data) > self.buffer_size:
  44. self.app.log.warning('request too long')
  45. self.transport.abort()
  46. self.untrusted_buffer.close()
  47. return
  48. self.len_untrusted_buffer += \
  49. self.untrusted_buffer.write(untrusted_data)
  50. def eof_received(self):
  51. try:
  52. src, method, dest, arg, untrusted_payload = \
  53. self.untrusted_buffer.getvalue().split(b'\0', 4)
  54. except ValueError:
  55. self.app.log.warning('framing error')
  56. self.transport.abort()
  57. return
  58. finally:
  59. self.untrusted_buffer.close()
  60. asyncio.ensure_future(self.respond(
  61. src, method, dest, arg, untrusted_payload=untrusted_payload))
  62. return True
  63. @asyncio.coroutine
  64. def respond(self, src, method, dest, arg, *, untrusted_payload):
  65. try:
  66. self.mgmt = self.handler(self.app, src, method, dest, arg,
  67. self.send_event)
  68. response = yield from self.mgmt.execute(
  69. untrusted_payload=untrusted_payload)
  70. assert not (self.event_sent and response)
  71. if self.transport is None:
  72. return
  73. # except clauses will fall through to transport.abort() below
  74. except qubes.api.PermissionDenied:
  75. self.app.log.warning(
  76. 'permission denied for call %s+%s (%s → %s) '
  77. 'with payload of %d bytes',
  78. method, arg, src, dest, len(untrusted_payload))
  79. except qubes.api.ProtocolError:
  80. self.app.log.warning(
  81. 'protocol error for call %s+%s (%s → %s) '
  82. 'with payload of %d bytes',
  83. method, arg, src, dest, len(untrusted_payload))
  84. except qubes.exc.QubesException as err:
  85. msg = ('%r while calling '
  86. 'src=%r method=%r dest=%r arg=%r len(untrusted_payload)=%d')
  87. if self.debug:
  88. self.app.log.exception(msg,
  89. err, src, method, dest, arg, len(untrusted_payload))
  90. else:
  91. self.app.log.info(msg,
  92. err, src, method, dest, arg, len(untrusted_payload))
  93. if self.transport is not None:
  94. self.send_exception(err)
  95. self.transport.write_eof()
  96. self.transport.close()
  97. return
  98. except Exception: # pylint: disable=broad-except
  99. self.app.log.exception(
  100. 'unhandled exception while calling '
  101. 'src=%r method=%r dest=%r arg=%r len(untrusted_payload)=%d',
  102. src, method, dest, arg, len(untrusted_payload))
  103. else:
  104. if not self.event_sent:
  105. self.send_response(response)
  106. try:
  107. self.transport.write_eof()
  108. except NotImplementedError:
  109. pass
  110. self.transport.close()
  111. return
  112. # this is reached if from except: blocks; do not put it in finally:,
  113. # because this will prevent the good case from sending the reply
  114. self.transport.abort()
  115. def send_header(self, *args):
  116. self.transport.write(self.header.pack(*args))
  117. def send_response(self, content):
  118. assert not self.event_sent
  119. self.send_header(0x30)
  120. if content is not None:
  121. self.transport.write(content.encode('utf-8'))
  122. def send_event(self, subject, event, **kwargs):
  123. self.event_sent = True
  124. self.send_header(0x31)
  125. if subject is not self.app:
  126. self.transport.write(subject.name.encode('ascii'))
  127. self.transport.write(b'\0')
  128. self.transport.write(event.encode('ascii') + b'\0')
  129. for k, v in kwargs.items():
  130. self.transport.write('{}\0{}\0'.format(k, str(v)).encode('ascii'))
  131. self.transport.write(b'\0')
  132. def send_exception(self, exc):
  133. self.send_header(0x32)
  134. self.transport.write(type(exc).__name__.encode() + b'\0')
  135. if self.debug:
  136. self.transport.write(''.join(traceback.format_exception(
  137. type(exc), exc, exc.__traceback__)).encode('utf-8'))
  138. self.transport.write(b'\0')
  139. self.transport.write(str(exc).encode('utf-8') + b'\0')
  140. def sighandler(loop, signame, *servers):
  141. print('caught {}, exiting'.format(signame))
  142. for server in servers:
  143. server.close()
  144. loop.stop()
  145. parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon')
  146. parser.add_argument('--debug', action='store_true', default=False,
  147. help='Enable verbose error logging (all exceptions with full '
  148. 'tracebacks) and also send tracebacks to Admin API clients')
  149. def main(args=None):
  150. loop = asyncio.get_event_loop()
  151. libvirtaio.virEventRegisterAsyncIOImpl(loop=loop)
  152. try:
  153. args = parser.parse_args(args)
  154. except:
  155. loop.close()
  156. raise
  157. args.app.vmm.register_event_handlers(args.app)
  158. try:
  159. os.unlink(QUBESD_SOCK)
  160. except FileNotFoundError:
  161. pass
  162. old_umask = os.umask(0o007)
  163. server = loop.run_until_complete(loop.create_unix_server(
  164. functools.partial(QubesDaemonProtocol, qubes.api.admin.QubesAdminAPI,
  165. app=args.app, debug=args.debug), QUBESD_SOCK))
  166. shutil.chown(QUBESD_SOCK, group='qubes')
  167. try:
  168. os.unlink(QUBESD_INTERNAL_SOCK)
  169. except FileNotFoundError:
  170. pass
  171. server_internal = loop.run_until_complete(loop.create_unix_server(
  172. functools.partial(QubesDaemonProtocol,
  173. qubes.api.internal.QubesInternalAPI,
  174. app=args.app, debug=args.debug), QUBESD_INTERNAL_SOCK))
  175. shutil.chown(QUBESD_INTERNAL_SOCK, group='qubes')
  176. try:
  177. os.unlink(QUBESD_MISC_SOCK)
  178. except FileNotFoundError:
  179. pass
  180. server_misc = loop.run_until_complete(loop.create_unix_server(
  181. functools.partial(QubesDaemonProtocol,
  182. qubes.api.misc.QubesMiscAPI,
  183. app=args.app, debug=args.debug), QUBESD_MISC_SOCK))
  184. shutil.chown(QUBESD_MISC_SOCK, group='qubes')
  185. os.umask(old_umask)
  186. del old_umask
  187. for signame in ('SIGINT', 'SIGTERM'):
  188. loop.add_signal_handler(getattr(signal, signame),
  189. sighandler, loop, signame, server, server_internal, server_misc)
  190. qubes.utils.systemd_notify()
  191. # make sure children will not inherit this
  192. os.environ.pop('NOTIFY_SOCKET', None)
  193. try:
  194. loop.run_forever()
  195. loop.run_until_complete(asyncio.wait([
  196. server.wait_closed(),
  197. server_internal.wait_closed(),
  198. ]))
  199. finally:
  200. loop.close()
  201. if __name__ == '__main__':
  202. main()