2013-03-20 05:24:17 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import os.path
|
|
|
|
import subprocess
|
2013-05-18 01:54:23 +00:00
|
|
|
from qubes.qubes import vmm
|
2013-03-20 05:24:17 +00:00
|
|
|
import qubes.guihelpers
|
|
|
|
from optparse import OptionParser
|
|
|
|
import fcntl
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
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"
|
|
|
|
|
|
|
|
class UserChoice:
|
|
|
|
ALLOW=0
|
|
|
|
DENY=1
|
|
|
|
ALWAYS_ALLOW=2
|
|
|
|
|
|
|
|
def line_to_dict(line):
|
|
|
|
tokens=line.split()
|
|
|
|
if len(tokens) < 3:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if tokens[0][0] == '#':
|
2013-10-27 18:22:47 +00:00
|
|
|
return None
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
dict={}
|
|
|
|
dict['source']=tokens[0]
|
|
|
|
dict['dest']=tokens[1]
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
dict['full-action']=tokens[2]
|
|
|
|
action_list=tokens[2].split(',')
|
|
|
|
dict['action']=action_list.pop(0)
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
for iter in action_list:
|
|
|
|
paramval=iter.split("=")
|
|
|
|
dict["action."+paramval[0]]=paramval[1]
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-12-27 19:29:50 +00:00
|
|
|
# Warn if we're ignoring extra data after a space, such as:
|
|
|
|
# vm1 vm2 allow, user=foo
|
|
|
|
if len(tokens) > 3:
|
|
|
|
print >>sys.stderr, "Trailing data ignored in %s" % line
|
|
|
|
|
2013-10-27 18:22:47 +00:00
|
|
|
return dict
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
|
|
|
|
def read_policy_file(exec_index):
|
|
|
|
policy_file=POLICY_FILE_DIR+"/"+exec_index
|
|
|
|
if not os.path.isfile(policy_file):
|
|
|
|
policy_file=DEPRECATED_POLICY_FILE_DIR+"/"+exec_index
|
|
|
|
if not os.path.isfile(policy_file):
|
|
|
|
return None
|
|
|
|
print >>sys.stderr, "RPC service '%s' uses deprecated policy location, please move to %s" % (exec_index, POLICY_FILE_DIR)
|
|
|
|
policy_list=list()
|
|
|
|
f = open(policy_file)
|
|
|
|
fcntl.flock(f, fcntl.LOCK_SH)
|
|
|
|
for iter in f.readlines():
|
|
|
|
dict = line_to_dict(iter)
|
|
|
|
if dict is not None:
|
|
|
|
policy_list.append(dict)
|
|
|
|
f.close()
|
|
|
|
return policy_list
|
|
|
|
|
|
|
|
def is_match(item, config_term):
|
|
|
|
return (item is not "dom0" and config_term == "$anyvm") or item == config_term
|
|
|
|
|
|
|
|
def get_default_policy():
|
|
|
|
dict={}
|
|
|
|
dict["action"]="deny"
|
|
|
|
return dict
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
def find_policy(policy, domain, target):
|
|
|
|
for iter in policy:
|
|
|
|
if not is_match(domain, iter["source"]):
|
|
|
|
continue
|
|
|
|
if not is_match(target, iter["dest"]):
|
|
|
|
continue
|
|
|
|
return iter
|
|
|
|
return get_default_policy()
|
|
|
|
|
|
|
|
def is_domain_running(target):
|
2013-05-18 01:54:23 +00:00
|
|
|
libvirt_dom = vmm.libvirt_conn.lookupByName(target)
|
2013-04-22 03:17:07 +00:00
|
|
|
if libvirt_dom:
|
|
|
|
return libvirt_dom.isActive()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2013-08-14 22:01:56 +00:00
|
|
|
def validate_target(target):
|
|
|
|
# special targets
|
|
|
|
if target in ['$dispvm', 'dom0']:
|
|
|
|
return True
|
|
|
|
|
|
|
|
from qubes.qubes import QubesVmCollection
|
|
|
|
|
|
|
|
qc = QubesVmCollection()
|
|
|
|
qc.lock_db_for_reading()
|
|
|
|
qc.load()
|
|
|
|
qc.unlock_db()
|
|
|
|
|
|
|
|
return qc.get_vm_by_name(target) is not None
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
def spawn_target_if_necessary(target):
|
|
|
|
if is_domain_running(target):
|
|
|
|
return
|
|
|
|
null=open("/dev/null", "r+")
|
|
|
|
subprocess.call(["qvm-run", "-a", "-q", target, "true"], stdin=null, stdout=null)
|
|
|
|
null.close()
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
def do_execute(domain, target, user, exec_index, process_ident):
|
|
|
|
if target == "dom0":
|
|
|
|
cmd="/usr/lib/qubes/qubes-rpc-multiplexer "+exec_index + " " + domain
|
|
|
|
elif target == "$dispvm":
|
|
|
|
cmd = "/usr/lib/qubes/qfile-daemon-dvm " + exec_index + " " + domain + " " +user
|
|
|
|
else:
|
2013-08-14 22:01:41 +00:00
|
|
|
# see the previous commit why "qvm-run -a" is broken and dangerous
|
|
|
|
# also, dangling "xl" would keep stderr open and may prevent closing connection
|
2013-03-20 05:24:17 +00:00
|
|
|
spawn_target_if_necessary(target)
|
2013-08-14 22:01:41 +00:00
|
|
|
cmd= QREXEC_CLIENT + " -d " + target + " '" + user
|
2013-03-20 05:24:17 +00:00
|
|
|
cmd+=":QUBESRPC "+ exec_index + " " + domain + "'"
|
2014-05-05 03:27:08 +00:00
|
|
|
# stderr should be logged in source/target VM
|
|
|
|
null = open(os.devnull, 'w')
|
|
|
|
os.dup2(null.fileno(), 2)
|
2013-03-20 05:24:17 +00:00
|
|
|
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", cmd, "-c", process_ident)
|
|
|
|
|
|
|
|
def confirm_execution(domain, target, exec_index):
|
|
|
|
text = "Do you allow domain \"" +domain + "\" to execute " + exec_index
|
|
|
|
text+= " operation on the domain \"" + target +"\"?<br>"
|
|
|
|
text+= " \"Yes to All\" option will automatically allow this operation in the future."
|
|
|
|
return qubes.guihelpers.ask(text, yestoall=True)
|
|
|
|
|
|
|
|
def add_always_allow(domain, target, exec_index, options):
|
|
|
|
policy_file=POLICY_FILE_DIR+"/"+exec_index
|
|
|
|
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 policy_editor(domain, target, exec_index):
|
|
|
|
text = "No policy definition found for " + exec_index + " action. "
|
|
|
|
text+= "Please create a policy file in Dom0 in " + POLICY_FILE_DIR + "/" + exec_index
|
2013-10-27 18:22:47 +00:00
|
|
|
subprocess.call(["/usr/bin/zenity", "--info", "--text", text])
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
def main():
|
|
|
|
usage = "usage: %prog [options] <src-domain> <target-domain> <service> <process-ident>"
|
|
|
|
parser = OptionParser (usage)
|
|
|
|
parser.add_option ("--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_option ("--just-evaluate", action="store_true", dest="just_evaluate", default=False,
|
|
|
|
help="Do not run the service, only evaluate policy; retcode=0 means 'allow'")
|
|
|
|
|
|
|
|
(options, args) = parser.parse_args ()
|
|
|
|
domain=args[0]
|
|
|
|
target=args[1]
|
|
|
|
exec_index=args[2]
|
|
|
|
process_ident=args[3]
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-08-14 22:01:56 +00:00
|
|
|
if not validate_target(target):
|
|
|
|
print >> sys.stderr, "Rpc failed (unknown domain):", domain, target, exec_index
|
2014-10-28 04:28:13 +00:00
|
|
|
text = "Domain '%s' doesn't exist (service %s called by domain %s)." % (
|
2013-08-14 22:01:56 +00:00
|
|
|
target, exec_index, domain)
|
|
|
|
subprocess.call(["/usr/bin/zenity", "--error", "--text", text])
|
|
|
|
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
|
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
policy_list=read_policy_file(exec_index)
|
|
|
|
if policy_list==None:
|
|
|
|
policy_editor(domain, target, exec_index)
|
|
|
|
policy_list=read_policy_file(exec_index)
|
|
|
|
if policy_list==None:
|
|
|
|
policy_list=list()
|
|
|
|
|
|
|
|
policy_dict=find_policy(policy_list, domain, target)
|
|
|
|
|
|
|
|
if policy_dict["action"] == "ask" and options.assume_yes_for_ask:
|
|
|
|
policy_dict["action"] = "allow"
|
2013-10-27 18:22:47 +00:00
|
|
|
|
2013-03-20 05:24:17 +00:00
|
|
|
if policy_dict["action"] == "ask":
|
|
|
|
user_choice = confirm_execution(domain, target, exec_index)
|
|
|
|
if user_choice == UserChoice.ALWAYS_ALLOW:
|
|
|
|
add_always_allow(domain, target, exec_index, 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 options.just_evaluate:
|
|
|
|
if policy_dict["action"] == "allow":
|
|
|
|
exit(0)
|
|
|
|
else:
|
2013-10-27 18:22:47 +00:00
|
|
|
exit(1)
|
2013-03-20 05:24:17 +00:00
|
|
|
|
|
|
|
if policy_dict["action"] == "allow":
|
|
|
|
if policy_dict.has_key("action.target"):
|
|
|
|
target=policy_dict["action.target"]
|
|
|
|
if policy_dict.has_key("action.user"):
|
|
|
|
user=policy_dict["action.user"]
|
|
|
|
else:
|
|
|
|
user="DEFAULT"
|
2013-10-27 18:23:09 +00:00
|
|
|
print >> sys.stderr, "Rpc allowed:", domain, target, exec_index
|
2013-03-20 05:24:17 +00:00
|
|
|
do_execute(domain, target, user, exec_index, process_ident)
|
|
|
|
|
2013-10-27 18:22:47 +00:00
|
|
|
print >> sys.stderr, "Rpc denied:", domain, target, exec_index
|
2013-03-20 05:24:17 +00:00
|
|
|
os.execl(QREXEC_CLIENT, "qrexec-client", "-d", domain, "-l", "/bin/false", "-c", process_ident)
|
|
|
|
|
|
|
|
main()
|