#!/usr/bin/python
import sys
import os
import os.path
import subprocess
import xen.lowlevel.xl
import qubes.guihelpers
import fcntl
 
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] == '#':
        return None  

    dict={}
    dict['source']=tokens[0]
    dict['dest']=tokens[1]
    
    dict['full-action']=tokens[2]
    action_list=tokens[2].split(',')
    dict['action']=action_list.pop(0)
    
    for iter in action_list:
        paramval=iter.split("=")
        dict["action."+paramval[0]]=paramval[1]
    
    return dict 


def read_policy_file(exec_index):
    policy_file=POLICY_FILE_DIR+"/"+exec_index
    if not os.path.isfile(policy_file):
        return None
    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
    

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):
    xl_ctx = xen.lowlevel.xl.ctx()         
    domains = xl_ctx.list_domains()
    for dominfo in domains:
        domname = xl_ctx.domid_to_name(dominfo.domid)
        if domname == target:
            return True
    return False
                
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()
                
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:
    # see the previous commit why "qvm-run -a" is broken and dangerous
    # also, dangling "xl" would keep stderr open and may prevent closing connection
        spawn_target_if_necessary(target)
        cmd= QREXEC_CLIENT + " -d " + target + " '" + user 
        cmd+=":QUBESRPC "+ exec_index + " " + domain + "'"
    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 = "Policy editor not yet implemented. Please add a line in the form \""
    text+= domain + " " + target + " action_to_take\""
    text+= " to /etc/qubes_rpc/policy/" + exec_index +" file in dom0, then close this info."
    subprocess.call(["/usr/bin/zenity", "--info", "--text", text]) 

def main():
    domain=sys.argv[1]
    target=sys.argv[2]
    exec_index=sys.argv[3]
    process_ident=sys.argv[4]
    
    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":
        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 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="user"
        do_execute(domain, target, user, exec_index, process_ident)

    print >> sys.stderr, "Rpc denied:", domain, target, exec_index    
    os.execl(QREXEC_CLIENT, "qrexec_client", "-d", domain, "-l", "/bin/false", "-c", process_ident)

main()