2017-02-02 13:03:08 +01:00
|
|
|
#!/usr/bin/env python3.6
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
import functools
|
|
|
|
import io
|
|
|
|
import os
|
2017-02-27 21:42:06 +01:00
|
|
|
import shutil
|
2017-02-02 13:03:08 +01:00
|
|
|
import signal
|
2017-02-17 15:48:22 +01:00
|
|
|
import struct
|
|
|
|
import traceback
|
2017-02-02 13:03:08 +01:00
|
|
|
|
2017-03-20 18:29:27 +01:00
|
|
|
import libvirtaio
|
|
|
|
|
2017-02-02 13:03:08 +01:00
|
|
|
import qubes
|
2017-02-08 18:44:08 +01:00
|
|
|
import qubes.mgmt
|
2017-03-21 11:48:20 +01:00
|
|
|
import qubes.mgmtinternal
|
2017-02-02 13:03:08 +01:00
|
|
|
import qubes.utils
|
|
|
|
import qubes.vm.qubesvm
|
|
|
|
|
|
|
|
QUBESD_SOCK = '/var/run/qubesd.sock'
|
2017-03-21 11:48:20 +01:00
|
|
|
QUBESD_INTERNAL_SOCK = '/var/run/qubesd.internal.sock'
|
2017-02-02 13:03:08 +01:00
|
|
|
|
|
|
|
class QubesDaemonProtocol(asyncio.Protocol):
|
|
|
|
buffer_size = 65536
|
2017-02-27 21:43:14 +01:00
|
|
|
header = struct.Struct('Bx')
|
2017-02-02 13:03:08 +01:00
|
|
|
|
2017-03-21 11:48:20 +01:00
|
|
|
def __init__(self, handler, *args, app, debug=False, **kwargs):
|
2017-02-08 15:20:15 +01:00
|
|
|
super().__init__(*args, **kwargs)
|
2017-03-21 11:48:20 +01:00
|
|
|
self.handler = handler
|
2017-02-02 13:03:08 +01:00
|
|
|
self.app = app
|
|
|
|
self.untrusted_buffer = io.BytesIO()
|
2017-02-08 15:20:15 +01:00
|
|
|
self.len_untrusted_buffer = 0
|
|
|
|
self.transport = None
|
2017-02-17 15:48:22 +01:00
|
|
|
self.debug = debug
|
2017-04-10 00:54:29 +02:00
|
|
|
self.mgmt = None
|
2017-02-02 13:03:08 +01:00
|
|
|
|
|
|
|
def connection_made(self, transport):
|
|
|
|
print('connection_made()')
|
|
|
|
self.transport = transport
|
|
|
|
|
|
|
|
def connection_lost(self, exc):
|
|
|
|
print('connection_lost(exc={!r})'.format(exc))
|
|
|
|
self.untrusted_buffer.close()
|
2017-04-10 00:54:29 +02:00
|
|
|
# for cancellable operation, interrupt it, otherwise it will do nothing
|
|
|
|
if self.mgmt is not None:
|
|
|
|
self.mgmt.cancel()
|
|
|
|
self.transport = None
|
2017-02-02 13:03:08 +01:00
|
|
|
|
2017-04-15 23:48:02 +02:00
|
|
|
def data_received(self, untrusted_data): # pylint: disable=arguments-differ
|
2017-02-02 13:03:08 +01:00
|
|
|
print('data_received(untrusted_data={!r})'.format(untrusted_data))
|
2017-02-08 15:20:15 +01:00
|
|
|
if self.len_untrusted_buffer + len(untrusted_data) > self.buffer_size:
|
2017-02-10 18:34:51 +01:00
|
|
|
self.app.log.warning('request too long')
|
|
|
|
self.transport.abort()
|
|
|
|
self.untrusted_buffer.close()
|
2017-02-02 13:03:08 +01:00
|
|
|
return
|
|
|
|
|
2017-02-08 15:20:15 +01:00
|
|
|
self.len_untrusted_buffer += \
|
2017-02-02 13:03:08 +01:00
|
|
|
self.untrusted_buffer.write(untrusted_data)
|
|
|
|
|
|
|
|
def eof_received(self):
|
|
|
|
print('eof_received()')
|
|
|
|
try:
|
|
|
|
src, method, dest, arg, untrusted_payload = \
|
|
|
|
self.untrusted_buffer.getvalue().split(b'\0', 4)
|
|
|
|
except ValueError:
|
2017-02-10 18:34:51 +01:00
|
|
|
self.app.log.warning('framing error')
|
|
|
|
self.transport.abort()
|
2017-02-02 13:03:08 +01:00
|
|
|
return
|
2017-02-10 18:34:51 +01:00
|
|
|
finally:
|
|
|
|
self.untrusted_buffer.close()
|
2017-02-02 13:03:08 +01:00
|
|
|
|
2017-03-01 17:17:10 +01:00
|
|
|
asyncio.ensure_future(self.respond(
|
|
|
|
src, method, dest, arg, untrusted_payload=untrusted_payload))
|
|
|
|
|
2017-03-10 23:56:00 +01:00
|
|
|
return True
|
|
|
|
|
2017-03-01 17:17:10 +01:00
|
|
|
@asyncio.coroutine
|
|
|
|
def respond(self, src, method, dest, arg, *, untrusted_payload):
|
2017-02-02 13:03:08 +01:00
|
|
|
try:
|
2017-04-10 00:54:29 +02:00
|
|
|
self.mgmt = self.handler(self.app, src, method, dest, arg,
|
|
|
|
self.send_event)
|
|
|
|
response = yield from self.mgmt.execute(
|
2017-03-01 17:17:10 +01:00
|
|
|
untrusted_payload=untrusted_payload)
|
2017-04-10 00:54:29 +02:00
|
|
|
if self.transport is None:
|
|
|
|
return
|
2017-02-10 18:34:51 +01:00
|
|
|
|
|
|
|
# except clauses will fall through to transport.abort() below
|
|
|
|
|
|
|
|
except qubes.mgmt.PermissionDenied:
|
|
|
|
self.app.log.warning(
|
|
|
|
'permission denied for call %s+%s (%s → %s) '
|
|
|
|
'with payload of %d bytes',
|
|
|
|
method, arg, src, dest, len(untrusted_payload))
|
|
|
|
|
|
|
|
except qubes.mgmt.ProtocolError:
|
|
|
|
self.app.log.warning(
|
|
|
|
'protocol error for call %s+%s (%s → %s) '
|
|
|
|
'with payload of %d bytes',
|
|
|
|
method, arg, src, dest, len(untrusted_payload))
|
|
|
|
|
2017-02-17 15:48:22 +01:00
|
|
|
except qubes.exc.QubesException as err:
|
2017-04-10 00:54:29 +02:00
|
|
|
self.app.log.exception(
|
|
|
|
'error while calling '
|
|
|
|
'src=%r method=%r dest=%r arg=%r len(untrusted_payload)=%d',
|
|
|
|
src, method, dest, arg, len(untrusted_payload))
|
|
|
|
if self.transport is not None:
|
|
|
|
self.send_exception(err)
|
|
|
|
self.transport.write_eof()
|
|
|
|
self.transport.close()
|
2017-02-17 15:48:22 +01:00
|
|
|
return
|
|
|
|
|
2017-02-10 18:34:51 +01:00
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
self.app.log.exception(
|
|
|
|
'unhandled exception while calling '
|
|
|
|
'src=%r method=%r dest=%r arg=%r len(untrusted_payload)=%d',
|
|
|
|
src, method, dest, arg, len(untrusted_payload))
|
|
|
|
|
|
|
|
else:
|
2017-02-17 15:48:22 +01:00
|
|
|
self.send_response(response)
|
2017-02-10 18:34:51 +01:00
|
|
|
try:
|
|
|
|
self.transport.write_eof()
|
|
|
|
except NotImplementedError:
|
|
|
|
pass
|
|
|
|
self.transport.close()
|
2017-02-02 13:03:08 +01:00
|
|
|
return
|
|
|
|
|
2017-02-10 18:34:51 +01:00
|
|
|
# this is reached if from except: blocks; do not put it in finally:,
|
|
|
|
# because this will prevent the good case from sending the reply
|
|
|
|
self.transport.abort()
|
2017-02-02 13:03:08 +01:00
|
|
|
|
|
|
|
|
2017-02-17 15:48:22 +01:00
|
|
|
def send_header(self, *args):
|
|
|
|
self.transport.write(self.header.pack(*args))
|
|
|
|
|
|
|
|
def send_response(self, content):
|
|
|
|
self.send_header(0x30)
|
2017-03-17 12:49:28 +01:00
|
|
|
if content is not None:
|
|
|
|
self.transport.write(content.encode('utf-8'))
|
2017-02-17 15:48:22 +01:00
|
|
|
|
|
|
|
def send_event(self, subject, event, **kwargs):
|
|
|
|
self.send_header(0x31)
|
|
|
|
|
|
|
|
if subject is not self.app:
|
|
|
|
self.transport.write(subject.name.encode('ascii'))
|
|
|
|
self.transport.write(b'\0')
|
|
|
|
|
|
|
|
self.transport.write(event.encode('ascii') + b'\0')
|
|
|
|
|
|
|
|
for k, v in kwargs.items():
|
|
|
|
self.transport.write('{}\0{}\0'.format(k, str(v)).encode('ascii'))
|
|
|
|
self.transport.write(b'\0')
|
|
|
|
|
|
|
|
def send_exception(self, exc):
|
|
|
|
self.send_header(0x32)
|
|
|
|
|
|
|
|
self.transport.write(type(exc).__name__ + b'\0')
|
|
|
|
|
|
|
|
if self.debug:
|
|
|
|
self.transport.write(''.join(traceback.format_exception(
|
|
|
|
type(exc), exc, exc.__traceback__)).encode('utf-8'))
|
|
|
|
self.transport.write(b'\0')
|
|
|
|
|
|
|
|
self.transport.write(str(exc).encode('utf-8') + b'\0')
|
|
|
|
|
|
|
|
|
2017-03-21 11:48:20 +01:00
|
|
|
def sighandler(loop, signame, server, server_internal):
|
2017-02-02 13:03:08 +01:00
|
|
|
print('caught {}, exiting'.format(signame))
|
|
|
|
server.close()
|
2017-03-21 11:48:20 +01:00
|
|
|
server_internal.close()
|
2017-02-02 13:03:08 +01:00
|
|
|
loop.stop()
|
|
|
|
|
|
|
|
parser = qubes.tools.QubesArgumentParser(description='Qubes OS daemon')
|
|
|
|
|
|
|
|
def main(args=None):
|
|
|
|
args = parser.parse_args(args)
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
2017-03-20 18:29:27 +01:00
|
|
|
libvirtaio.virEventRegisterAsyncIOImpl(loop=loop)
|
2017-02-02 13:03:08 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
os.unlink(QUBESD_SOCK)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
old_umask = os.umask(0o007)
|
|
|
|
server = loop.run_until_complete(loop.create_unix_server(
|
2017-03-21 11:48:20 +01:00
|
|
|
functools.partial(QubesDaemonProtocol, qubes.mgmt.QubesMgmt,
|
|
|
|
app=args.app), QUBESD_SOCK))
|
2017-02-27 21:42:06 +01:00
|
|
|
shutil.chown(QUBESD_SOCK, group='qubes')
|
2017-03-21 11:48:20 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
os.unlink(QUBESD_INTERNAL_SOCK)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
server_internal = loop.run_until_complete(loop.create_unix_server(
|
|
|
|
functools.partial(QubesDaemonProtocol,
|
|
|
|
qubes.mgmtinternal.QubesInternalMgmt,
|
|
|
|
app=args.app), QUBESD_INTERNAL_SOCK))
|
|
|
|
shutil.chown(QUBESD_INTERNAL_SOCK, group='qubes')
|
|
|
|
|
2017-02-02 13:03:08 +01:00
|
|
|
os.umask(old_umask)
|
|
|
|
del old_umask
|
|
|
|
|
|
|
|
for signame in ('SIGINT', 'SIGTERM'):
|
|
|
|
loop.add_signal_handler(getattr(signal, signame),
|
2017-03-21 11:48:20 +01:00
|
|
|
sighandler, loop, signame, server, server_internal)
|
2017-02-02 13:03:08 +01:00
|
|
|
|
|
|
|
qubes.utils.systemd_notify()
|
|
|
|
|
|
|
|
try:
|
|
|
|
loop.run_forever()
|
2017-03-21 11:48:20 +01:00
|
|
|
loop.run_until_complete(asyncio.wait([
|
|
|
|
server.wait_closed(),
|
|
|
|
server_internal.wait_closed(),
|
|
|
|
]))
|
2017-02-02 13:03:08 +01:00
|
|
|
finally:
|
|
|
|
loop.close()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|