#!/usr/bin/env python3 # # The Qubes OS Project, https://www.qubes-os.org/ # # Copyright (C) 2017 Wojtek Porczyk # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, see . # import logging import os import string import sys import pathlib POLICY_PATH = pathlib.Path('/etc/qubes-rpc/policy') POLICY_RULE = '{frontend} {backend} allow\n' # linux-utils/qrexec-lib/qrexec.h MAX_ARGUMENT_LEN = 64 # core-admin-linux/qrexec/qrexec-daemon.c VALID_CHARS = set(map(ord, string.ascii_letters + string.digits + '-._')) def die(*args, **kwargs): logging.error(*args, **kwargs) sys.exit(1) def main(): # pylint: disable=missing-docstring logging.basicConfig( level=logging.WARNING, filename='/var/log/qubes/policy-register.log', format='%(asctime)s %(message)s') backend = os.environ['QREXEC_REMOTE_DOMAIN'] frontend = os.environ['QREXEC_REQUESTED_TARGET'] rpcname = os.environ['QREXEC_SERVICE_ARGUMENT'] logging.debug('%s %s → %s request, reading argument', rpcname, frontend, backend) untrusted_argument = sys.stdin.buffer.read(MAX_ARGUMENT_LEN) untrusted_overflow = sys.stdin.buffer.read(1) sys.stdin.buffer.close() if untrusted_overflow: die('%s: %s → %s request refused: argument too long', rpcname, frontend, backend) if not untrusted_argument: die('%s: %s → %s request refused: empty argument', rpcname, frontend, backend) if any(c not in VALID_CHARS for c in untrusted_argument): die('%s: %s → %s request refused: invalid argument', rpcname, frontend, backend) # argument may also be too long, so that length of rpcname, separator and # argument exceed 64 bytes, but that's fine, the call just wont work argument = untrusted_argument del untrusted_argument argument = argument.decode('ascii') filename = '{}+{}'.format(rpcname, argument) logging.debug('%s %s → %s argument %s filename %s', rpcname, frontend, backend, argument, filename) try: # the 'x' is critical with open(str(POLICY_PATH / filename), 'x') as file: rule = POLICY_RULE.format(frontend=frontend, backend=backend) logging.warning('%s: %s → %s %s argument allowed', rpcname, frontend, backend, argument) logging.debug('%s: %s → %s %s adding rule %r', rpcname, frontend, backend, rule) file.write(rule) except FileExistsError: die('%s: %s → %s %s argument failed: file exists') if __name__ == '__main__': main()