#!/usr/bin/python3
import configparser
import gettext
import os
import pwd
import re
import subprocess
import sys
import tempfile

_ = gettext.gettext
gettext.textdomain("arkose-wrapper-gui")

if len(sys.argv) == 1:
    print(_("Usage: %s <profile> [start]") % sys.argv[0])
    sys.exit(1)

if not os.path.exists(sys.argv[1]):
    print(_("File not found: %s") % sys.argv[1])
    sys.exit(1)

profile_schema = ['cmd',
                  'runas',
                  'network',
                  'xserver',
                  'dbus',
                  'pulseaudio',
                  'video',
                  'root',
                  'mount_bind',
                  'mount_cow',
                  'mount_restrict']

# Get the current user
user = pwd.getpwuid(int(os.getenv(
    "PKEXEC_UID", os.getenv("SUDO_UID", os.getenv("UID", os.geteuid())))))

# Initialize the config
running_config = open(sys.argv[1], "r").read()
config = None
name = None
profile = None
dbus = None
dbusproxy = None


def configobj_style(configp):
    config = {}

    for section in configp.sections():
        config_section = {}
        for option in configp.options(section):
            value = configp.get(section, option)
            if "," in value:
                value = [entry.strip('"') for entry in value.split(",")]
            else:
                value = value.strip('"')
            config_section[option] = value
        config[section] = config_section

    return config


def refresh_config():
    global config, name, profile, dbus, dbusproxy

    configp = configparser.ConfigParser()
    configp.read_string(running_config)

    config = configobj_style(configp)

    name = os.path.split(sys.argv[1])[-1]
    profile = config['container']

    if "dbus-proxy" in config:
        dbus = config['dbus-proxy']
    else:
        dbus = None

    if not set(profile) == set(profile_schema):
        print(_("Invalid profile"))
        sys.exit(1)

    # Some type conversion
    if profile['network'] in ("true", "True"):
        profile['network'] = True
    elif profile['network'] in ("false", "False"):
        profile['network'] = False

    profile["pulseaudio"] = profile["pulseaudio"] in ("true", "True")
    profile["video"] = profile["video"] in ("true", "True")

    if not "devices" in profile:
        profile['devices'] = []

    if dbus and "session" in dbus:
        dbusproxy = dbus['session']
        if type(dbusproxy) != list:
            dbusproxy = [dbusproxy]
    else:
        dbusproxy = ['*;*;*;*']

    for mount in ("mount_bind", "mount_cow", "mount_restrict"):
        if not profile[mount]:
            profile[mount] = []
        elif type(profile[mount]) == str:
            profile[mount] = [profile[mount]]

        for entry in profile[mount]:
            new = re.sub("^\$HOME/", "%s/" %
                         user.pw_dir, entry)
            new = re.sub("\$USER", "%s" %
                         user.pw_name, new)

            profile[mount].remove(entry)
            profile[mount].append(new)

refresh_config()

if len(sys.argv) == 3 and sys.argv[2] == "start":
    # Work around pkexec not keeping PWD
    if 'PWD' in os.environ:
        os.chdir(os.getenv("PWD"))
    currentdir = os.path.split(os.path.realpath(__file__))[0]
    if os.path.exists(os.path.join(currentdir, "../arkose/__init__.py")):
        sys.path.insert(0, os.path.join(currentdir, ".."))

    if not os.geteuid() == 0:
        print(_("You must be root to start this command"))
        sys.exit(1)

    if (profile["dbus"] in ("session", "both") and
            not os.getenv("DBUS_SESSION_BUS_ADDRESS", None)):
        print(_("Asked for dbus session support but no "
                "DBUS_SESSION_BUS_ADDRESS"))
        sys.exit(1)

    if profile["runas"] == "user" and not os.getenv("PKEXEC_UID", None):
        print(_("Missing PKEXEC_UID"))
        sys.exit(1)

    if "/tmp" in sys.argv[1]:
        os.remove(sys.argv[1])

    # We seem to be ready, let's start
    import arkose

    if profile['video']:
        import glob
        profile['devices'] += glob.glob("/dev/video*")

    dbusproxy_config = []
    for entry in dbusproxy:
        if re.match("^.*;.*;.*;.*$", entry):
            # Append valid dbusproxy rules
            dbusproxy_config.append(entry)
        else:
            for path in ["dbus-proxy/profiles/%s.conf" % entry,
                         "/usr/share/arkose/dbus-profiles/%s.conf" % entry]:
                # When not valid, check if that's a valid profile name
                if os.path.exists(path):
                    # Parse the profile and append the new rules
                    for line in open(path):
                        if re.match("^.*;.*;.*;.*$", line):
                            dbusproxy_config.append(line)

                    # Only parse one file if multiple exist
                    break

    container = arkose.ArkoseContainer(
        dbus=profile["dbus"],
        dbusproxy=dbusproxy_config,
        xserver=profile["xserver"],
        network=profile["network"],
        pulseaudio=profile["pulseaudio"],
        devices=profile['devices'],
        root=profile['root'],
        bind=profile["mount_bind"],
        cow=profile["mount_cow"],
        restrict=profile["mount_restrict"]
    )

    try:
        if profile["runas"] == "user":
            container.run_command("su %s -c \"%s\"" % (user.pw_name,
                                                       profile["cmd"]))
        else:
            subprocess.call(["xhost", "+SI:localuser:root"])
            container.run_command("%s" % profile["cmd"])
            subprocess.call(["xhost", "-SI:localuser:root"])
        container.cleanup()
    except KeyboardInterrupt:
        container.cleanup()

    # Done, exiting
    sys.exit(0)

# Otherwise, start the UI
from gi.repository import Gtk

builder = Gtk.Builder()
builder.set_translation_domain("arkose-wrapper-gui")
if os.path.exists("wrapper/arkose-wrapper-gui.xml"):
    xml_file = "wrapper/arkose-wrapper-gui.xml"
elif os.path.exists("/usr/share/arkose/gui/arkose-wrapper-gui.xml"):
    xml_file = "/usr/share/arkose/gui/arkose-wrapper-gui.xml"
else:
    print(_("Unable to find .xml UI file"))
    sys.exit(1)

builder.add_from_file(xml_file)

winArkose = builder.get_object("winArkose")
winArkoseEdit = builder.get_object("winArkoseEdit")
tvConfig = builder.get_object("tvConfig")

configText = Gtk.TextBuffer()

tvConfig.set_buffer(configText)
lblWarning = builder.get_object("lblWarning")
lblAccess = builder.get_object("lblAccess")
start = False


def refresh_ui():
    winArkose.set_title(_("Starting %s") % name)

    lblWarning.set_markup("<b>%s</b>" %
                          _("Restricted application: %s") % name +
                          "\n<small>" + _("In order to work properly, "
                                          """the application you started
requires some of the access rights listed below.
Please review them. If approved, click Execute.""") + "</small>")

    access = "<b>%s</b>" % _("Rights:")
    access += "\n - %s" % (_("Run the following command: <b>%s</b>") %
                           profile['cmd'])
    if profile['runas'] == 'root':
        access += "\n - %s" % (_("Run software as the <b>root user</b>."))

    if profile['network'] == "direct":
        access += "\n - %s" % (_("Direct and unlimited access to the network"))
    elif profile['network'] == "filtered":
        access += "\n - %s" % (_("Filtered access to the network"))

    if profile['xserver'] == "direct":
        access += "\n - %s" % (_("Direct access to your X server"))
    elif profile['xserver'] == "isolated":
        access += "\n - %s" % (_("Isolated access to the X server"))

    if profile['dbus'] in ("session", "both"):
        access += "\n - %s" % (_("Access the DBUS session bus"))

    if profile['dbus'] in ("system", "both"):
        access += "\n - %s" % (_("Access the DBUS system bus"))

    if profile['pulseaudio']:
        access += "\n - %s" % (_("Play/Record sound (pulseaudio)"))

    if profile['video']:
        access += "\n - %s" % (_("Access your video devices (webcam)"))

    if profile['root'] != "/":
        access += "\n - %s" % (_("Use alternate root: %s") % profile['root'])

    if (len(profile['mount_bind']) > 0 or
            len(profile['mount_cow']) > 0 or
            len(profile['mount_restrict']) > 0):
        access += "\n\n<b>%s</b>" % _("Files and directories:")
        if len(profile['mount_bind']) > 0:
            access += "\n - %s" % _("Direct access: <b>%s</b>") % ", ".join(
                      profile['mount_bind'])
        if len(profile['mount_cow']) > 0:
            access += "\n - %s" % _("Copy-on-Write: <b>%s</b>") % ", ".join(
                      profile['mount_cow'])
        if len(profile['mount_restrict']) > 0:
            access += "\n - %s" % _("Temporary: <b>%s</b>") % ", ".join(
                      profile['mount_restrict'])

    if len(dbusproxy) > 0:
        access += "\n\n<b>%s</b>" % _("DBUS session bus access:")
        for rule in dbusproxy:
            if rule == "*;*;*;*":
                access += "\n - %s" % _("Everything")
            else:
                access += "\n - %s" % rule

    lblAccess.set_markup(access)

refresh_ui()


def execute(button):
    global start
    start = True
    winArkose.hide()
    Gtk.main_quit()


def apply_edit(button):
    global running_config
    running_config = configText.get_text(configText.get_start_iter(),
                                         configText.get_end_iter(), True)
    winArkoseEdit.hide()

    # Refresh the UI
    refresh_config()
    refresh_ui()


def hide_edit(button):
    configText.set_text(running_config)
    winArkoseEdit.hide()


def show_edit(button):
    configText.set_text(running_config)
    winArkoseEdit.show()

builder.connect_signals({
    "on_winArkose_destroy": Gtk.main_quit,
    "on_btnCancel_clicked": Gtk.main_quit,
    "on_btnCancel1_clicked": hide_edit,
    "on_btnEdit_clicked": show_edit,
    "on_btnExec_clicked": execute,
    "on_btnApply_clicked": apply_edit,
})

winArkose.show()
Gtk.main()

if start:
    env_whitelist = ["PWD", "PATH", "HOME", "UBUNTU_MENUPROXY",
                     "XDG_RUNTIME_DIR"]
    if profile["xserver"] != "none":
        env_whitelist += ["DISPLAY", "XAUTHORITY"]

    if profile["pulseaudio"]:
        env_whitelist += ["PULSE_SERVER"]

    if profile["dbus"] in ("session", "both"):
        env_whitelist.append("DBUS_SESSION_BUS_ADDRESS")

    env = []
    for variable in env_whitelist:
        if variable == "UBUNTU_MENUPROXY" and profile['xserver'] == "isolated":
            # Global menu doesn't understand windows rendered on
            # another X server
            continue

        value = os.getenv(variable, None)
        if value:
            env.append("%s=%s" % (variable, value))

    if profile['xserver'] == "isolated":
        screen = winArkose.get_screen()
        env.append("XPRA_XVFB_RESOLUTION=%sx%s" % (screen.get_width(),
                                                   screen.get_height()))

    config_path = tempfile.mktemp()
    tmpconfig = open(config_path, "w+")
    tmpconfig.write(running_config)
    tmpconfig.close()

    cmd = ["pkexec", "env"]
    cmd += env
    cmd += [os.path.realpath(sys.argv[0]), os.path.realpath(config_path),
            "start"]

    subprocess.call(cmd)
