diff --git a/qrexec/qrexec-daemon.c b/qrexec/qrexec-daemon.c index 6df3ebf..7920b7d 100644 --- a/qrexec/qrexec-daemon.c +++ b/qrexec/qrexec-daemon.c @@ -712,7 +712,7 @@ static void handle_execute_service(void) signal(SIGPIPE, SIG_DFL); snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d", remote_domain_id); - execl("/usr/lib/qubes/qrexec-policy", "qrexec-policy", "--", + execl("/usr/bin/qrexec-policy", "qrexec-policy", "--", remote_domain_id_str, remote_domain_name, params.target_domain, params.service_name, params.request_id.ident, NULL); perror("execl"); diff --git a/qrexec/qrexec-policy b/qrexec/qrexec-policy deleted file mode 100755 index 9070fed..0000000 --- a/qrexec/qrexec-policy +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/python3 - -import argparse -import sys -import os -import os.path -import subprocess -import qubes -import libvirt -from optparse import OptionParser -import fcntl -from PyQt4.QtGui import QApplication, QMessageBox - -POLICY_FILE_DIR = "/etc/qubes-rpc/policy" -# XXX: Backward compatibility, to be removed soon -DEPRECATED_POLICY_FILE_DIR = "/etc/qubes_rpc/policy" -QREXEC_CLIENT = "/usr/lib/qubes/qrexec-client" -QUBES_RPC_MULTIPLEXER_PATH = "/usr/lib/qubes/qubes-rpc-multiplexer" - - -class UserChoice: - ALLOW = 0 - DENY = 1 - ALWAYS_ALLOW = 2 - - -def prepare_app(): - app = QApplication(sys.argv) - app.setOrganizationName("The Qubes Project") - app.setOrganizationDomain("http://qubes-os.org") - app.setApplicationName("Qubes") - return app - - -def ask(text, title="Question", yestoall=False): - qtapp = prepare_app() - - buttons = QMessageBox.Yes | QMessageBox.No - if yestoall: - buttons |= QMessageBox.YesToAll - - reply = QMessageBox.question(None, title, text, buttons, - defaultButton=QMessageBox.Yes) - if reply == QMessageBox.Yes: - return 0 - elif reply == QMessageBox.No: - return 1 - elif reply == QMessageBox.YesToAll: - return 2 - else: - # ?! - return 127 - - -def line_to_dict(line): - tokens = line.split() - if len(tokens) < 3: - return None - - if tokens[0][0] == '#': - return None - - policy_dict = { - 'source': tokens[0], - 'dest': tokens[1], - 'full-action': tokens[2], - } - - action_list = tokens[2].split(',') - policy_dict['action'] = action_list.pop(0) - - for action_iter in action_list: - paramval = action_iter.split("=") - policy_dict["action." + paramval[0]] = paramval[1] - - # Warn if we're ignoring extra data after a space, such as: - # vm1 vm2 allow, user=foo - if len(tokens) > 3: - print("Trailing data ignored in %s" % line, file=sys.stderr) - - return policy_dict - - -def read_policy_file(service_name): - policy_file = os.path.join(POLICY_FILE_DIR, service_name) - if not os.path.isfile(policy_file): - # fallback to policy without specific argument set (if any) - policy_file = os.path.join(POLICY_FILE_DIR, service_name.split("+")[0]) - if not os.path.isfile(policy_file): - policy_file = os.path.join(DEPRECATED_POLICY_FILE_DIR, service_name) - if not os.path.isfile(policy_file): - return None - print(sys.stderr, - "RPC service '%s' uses deprecated policy location, " - "please move to %s" % (service_name, POLICY_FILE_DIR), - file=sys.stderr) - policy_list = list() - f = open(policy_file) - fcntl.flock(f, fcntl.LOCK_SH) - for policy_iter in f.readlines(): - policy_item = line_to_dict(policy_iter) - if policy_item is not None: - policy_list.append(policy_item) - f.close() - return policy_list - - -def is_match(config_term, item): - if config_term == '$anyvm': - # match anything but dom0 - return item != "dom0" - else: - if isinstance(item, qubes.vm.qubesvm.QubesVM): - return config_term == item.name - else: - return config_term == item - - -def get_default_policy(): - return {"action": "deny"} - - -def find_policy(policy, source_domain, target, target_domain=None): - for policy_iter in policy: - if not is_match(policy_iter["source"], source_domain): - continue - if not is_match(policy_iter["dest"], target_domain or target): - continue - return policy_iter - return get_default_policy() - - -def validate_target(app, target): - """Validate target name. Throw KeYError for invalid name. - - :param app: Qubes app object - :param target: target name to validate - :return: QubesVM object or None (in case of spacial target) - """ - # special targets - if target == '$dispvm' or target.startswith('$dispvm:'): - return None - - return app.domains[target] - - -def spawn_target_if_necessary(vm): - if vm.is_running(): - return - # TODO: tray notification - vm.start() - -def do_execute(domain, target, user, service_name, process_ident, vm=None): - dispvm = False - if target == "$dispvm": - if domain.default_dispvm is None: - print("No default DispVM set, aborting!", file=sys.stderr) - exit(1) - target = "$dispvm:" + domain.default_dispvm.name - if target.startswith("$dispvm:"): - dispvm_tpl_name = target[len("$dispvm:"):] - vm = qubes.vm.dispvm.DispVM.from_appvm(dispvm_tpl_name) - dispvm = True - # at this point we should also have some VM *object* - assert vm is not None - try: - spawn_target_if_necessary(vm) - if target == "dom0": - cmd = QUBES_RPC_MULTIPLEXER_PATH + " " + service_name + " " + \ - domain.name - else: - cmd = user + ":QUBESRPC " + service_name + " " + domain.name - qrexec_opts = ["-d", vm.name, "-c", process_ident] - if dispvm: - # wait for qrexec connection end - qrexec_opts.append("-W") - subprocess.call([QREXEC_CLIENT] + qrexec_opts + [cmd]) - finally: - if dispvm: - vm.cleanup() - -def confirm_execution(domain, target, service_name): - text = "Do you allow domain \"" + domain + "\" to execute " + service_name - text += " operation on the domain \"" + target + "\"?
" - text += " \"Yes to All\" option will automatically allow this " \ - "operation in the future." - return ask(text, yestoall=True) - - -def add_always_allow(domain, target, service_name, options): - policy_file = POLICY_FILE_DIR + "/" + service_name - if not os.path.isfile(policy_file): - return None - f = open(policy_file, 'r+') - fcntl.flock(f, fcntl.LOCK_EX) - lines = [] - for l in f.readlines(): - lines.append(l) - lines.insert(0, "%s\t%s\tallow%s\n" % (domain, target, options)) - f.seek(0) - f.write("".join(lines)) - f.close() - - -def info_dialog(msg_type, text): - if msg_type not in ['info', 'warning', 'error']: - raise ValueError("Invalid msg_type value") - try: - subprocess.call(["/usr/bin/zenity", "--{}".format(msg_type), "--text", - text]) - except OSError: - kdialog_msg_type = { - 'info': 'msgbox', - 'warning': 'sorry', - 'error': 'error' - }[msg_type] - subprocess.call(["/usr/bin/kdialog", "--{}".format(kdialog_msg_type), - text]) - - -# noinspection PyUnusedLocal -def policy_editor(domain, target, service_name): - text = "No policy definition found for " + service_name + " action. " - text += "Please create a policy file in Dom0 in " + POLICY_FILE_DIR + "/" + service_name - info_dialog("warning", text) - - -def main(): - parser = argparse.ArgumentParser(description="Evaluate qrexec policy") - parser.add_argument("--assume-yes-for-ask", action="store_true", - dest="assume_yes_for_ask", default=False, - help="Allow run of service without confirmation if policy say 'ask'") - parser.add_argument("--just-evaluate", action="store_true", - dest="just_evaluate", default=False, - help="Do not run the service, only evaluate policy; " - "retcode=0 means 'allow'") - parser.add_argument('domain_id', metavar='src-domain-id', - help='Source domain ID (Xen ID or similar, not Qubes ID)') - parser.add_argument('domain', metavar='src-domain-name', - help='Source domain name') - parser.add_argument('target', metavar='dst-domain-name', - help='Target domain name') - parser.add_argument('service_name', metavar='service-name', - help='Service name') - parser.add_argument('process_ident', metavar='proces-ident', - help='Qrexec process identifier - for connecting data channel') - - args = parser.parse_args() - process_ident = args.process_ident - - # Add source domain information, required by qrexec-client for establishing - # connection - process_ident += "," + args.domain + "," + args.domain_id - - app = qubes.Qubes() - - try: - source_vm = app.domains[args.domain] - except KeyError: - print("Rpc failed (unknown source domain): ", - args.domain, args.target, args.service_name, file=sys.stderr) - text = "Domain '%s' doesn't exist (service %s called to domain %s)." % ( - args.domain, args.service_name, args.target) - info_dialog("error", text) - return 1 - - try: - target_vm = validate_target(app, args.target) - except KeyError: - print("Rpc failed (unknown domain):", - args.domain, args.target, args.service_name, file=sys.stderr) - text = "Domain '%s' doesn't exist (service %s called by domain %s)." % ( - args.target, args.service_name, args.domain) - info_dialog("error", text) - return 1 - - policy_list = read_policy_file(args.service_name) - if policy_list is None: - policy_editor(args.domain, args.target, args.service_name) - policy_list = read_policy_file(args.service_name) - if policy_list is None: - policy_list = list() - - policy_dict = find_policy(policy_list, source_vm, args.target, target_vm) - - if policy_dict["action"] == "ask" and args.assume_yes_for_ask: - policy_dict["action"] = "allow" - - if policy_dict["action"] == "ask": - user_choice = confirm_execution(args.domain, args.target, args.service_name) - if user_choice == UserChoice.ALWAYS_ALLOW: - add_always_allow(args.domain, args.target, args.service_name, - policy_dict["full-action"].lstrip('ask')) - policy_dict["action"] = "allow" - elif user_choice == UserChoice.ALLOW: - policy_dict["action"] = "allow" - else: - policy_dict["action"] = "deny" - - if args.just_evaluate: - if policy_dict["action"] == "allow": - return 0 - else: - return 1 - - if policy_dict["action"] == "allow": - if "action.target" in policy_dict: - args.target = policy_dict["action.target"] - if "action.user" in policy_dict: - user = policy_dict["action.user"] - else: - user = "DEFAULT" - print("Rpc allowed:", args.domain, args.target, - args.service_name, file=sys.stderr) - do_execute(source_vm, args.target, user, args.service_name, process_ident, - vm=target_vm) - return 0 - print("Rpc denied:", args.domain, args.target, args.service_name, - file=sys.stderr) - return 1 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/rpm_spec/core-dom0-linux.spec b/rpm_spec/core-dom0-linux.spec index e955a0b..33a9638 100644 --- a/rpm_spec/core-dom0-linux.spec +++ b/rpm_spec/core-dom0-linux.spec @@ -113,7 +113,6 @@ cp qrexec/qrexec-daemon $RPM_BUILD_ROOT/usr/lib/qubes/ cp qrexec/qrexec-client $RPM_BUILD_ROOT/usr/lib/qubes/ # XXX: Backward compatibility ln -s qrexec-client $RPM_BUILD_ROOT/usr/lib/qubes/qrexec_client -cp qrexec/qrexec-policy $RPM_BUILD_ROOT/usr/lib/qubes/ cp qrexec/qubes-rpc-multiplexer $RPM_BUILD_ROOT/usr/lib/qubes ### pm-utils @@ -250,7 +249,6 @@ chmod -x /etc/grub.d/10_linux /usr/lib/qubes/qrexec-client /usr/lib/qubes/qrexec_client /usr/lib/qubes/qubes-rpc-multiplexer -/usr/lib/qubes/qrexec-policy # file copy /usr/bin/qvm-copy-to-vm /usr/bin/qvm-move-to-vm