#! /usr/bin/python3
# wg-systemd-event-handler
#   Handle event for wg_systemd
# Copyright 2020 WatchGuard Technologies, Inc.

from __future__ import print_function, division, generators, unicode_literals

import argparse
import errno
import json
import logging
import os
import socket
import stat
import subprocess
import sys

# Try to import the dynamic glib, or try to fall back to static
try:
    from gi.repository import GLib as glib  # pragma: no cover
except ImportError:                         # pragma: no cover
    import glib                             # pragma: no cover

import dbus
import dbus.service
import dbus.mainloop.glib

logger = logging.getLogger('wg-systemd-event-handler')

LOG_FORMAT = '%(levelname)s:%(message)s'

# Bus name
NAME = 'com.watchguard.system'
# Interface name
IFACE = 'com.watchguard.system.EventHandler'
# Object path
OBJECT = '/com/watchguard/system/EventObject'

# Method call return strings
SUCCESS = 'success'
FAILED = 'failed'

# Global environment variables file
ENV_FILE = '/etc/default/wg_system'


def sd_notify(**kwargs):
    """Systemd sd_notify implementation for Python.
    Note: kwargs should contain the state to send to systemd"""
    if not kwargs:
        logger.error("sd_notify called with no state specified!")
        return -errno.EINVAL
    sock = None
    try:
        # Turn state, a dictionary, into a properly formatted string where
        # each 'key=val' combo in the dictionary is separated by a \n
        state_str = '\n'.join(['{0}={1}'.format(key, val) for (key, val)
                               in kwargs.items()])
        env = os.environ.get('NOTIFY_SOCKET', None)
        if not env:
            # Process was not invoked with systemd
            return -errno.EINVAL
        if env[0] not in ('/', '@'):
            logger.warning("NOTIFY_SOCKET is set, but does not contain a "
                           "legitimate value")
            return -errno.EINVAL
        if env[0] == '@':
            env = '\0' + env[1:]
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
        if sock.sendto(bytearray(state_str, 'utf-8'), env) > 0:
            return 1
    # pylint: disable=broad-except
    except Exception:
        logger.exception("Ignoring unexpected error during sd_notify() "
                         "invocation")

    if sock:
        sock.close()

    return 0


def parse_args(args):
    """Parses arguments from command line"""
    ap = argparse.ArgumentParser(description='WatchGuard system event handler')
    ap.add_argument('-v', '--verbose', action='count', default=0,
                    help='Increment verbosity level once per call')
    ap.add_argument('-q', '--quiet', action='count', default=0,
                    help='Decrement verbosity level once per call')
    return ap.parse_args(args)


class EventObject(dbus.service.Object):
    def __init__(self, conn, object_path=OBJECT):
        dbus.service.Object.__init__(self, conn, object_path)

    @dbus.service.method(IFACE, in_signature='sss', out_signature='s')
    def wg_event(self, wg_event_type, wg_file, wg_op):
        status = FAILED
        logger.info('Call to wg_event(wg_event_type={0}, wg_file={1}, wg_op={2})'.format(wg_event_type, wg_file, wg_op))
        bindir = os.environ.get('WG_BINDIR')
        script = os.path.join(bindir, 'wg_event.sh')
        if os.path.isfile(script):
            entry = os.stat(script)
            # Make sure script can be executed
            if not stat.S_IXUSR & entry.st_mode:
                logger.error("Unable to execute script, check file mode: "
                             "%s", script)
            ret = subprocess.Popen([script, wg_event_type, wg_file, wg_op]).wait()
            if ret != 0:
                logger.warning('Exit status %r from script %r invoked', ret, script)
            else:
                status = SUCCESS
        return status

    @dbus.service.method(IFACE, in_signature=None, out_signature='s')
    def SystemReset(self):
        logger.info('Call to SystemReset()')
        ret = 0
        # Restart wg-system-reset service
        ret |= subprocess.Popen(['systemctl', 'restart', 'wg-system-reset']).wait()
        # Restart wg-backup service
        ret |= subprocess.Popen(['systemctl', 'restart', 'wg-backup']).wait()
        # Restart wgdevice-ssh-init and wgdevice-ssh services
        ret |= subprocess.Popen(['systemctl', 'restart', 'wgdevice-ssh-init']).wait()
        ret |= subprocess.Popen(['systemctl', 'restart', 'wgdevice-ssh']).wait()
        status = SUCCESS if ret == 0 else FAILED
        return status

    @dbus.service.method(IFACE, in_signature='s', out_signature='s')
    def RestartService(self, service):
        logger.info('Call to RestartService(service={0})'.format(service))
        import time
        time.sleep(1)
        ret = subprocess.Popen(['systemctl', 'restart', service]).wait()
        status = SUCCESS if ret == 0 else FAILED
        return status


def import_env():
    if not os.path.exists(ENV_FILE):
        logger.error("Environment file: %s is not existed.", ENV_FILE)
        return
    try:
        with open(ENV_FILE, "r") as f:
            lines = f.readlines()
        for line in lines:
            var = line.strip().split('=')
            key, value = var[0], var[1].strip('"')
            os.environ[key] = value
    except Exception:
        logger.exception("Error importing environment file: {0}.".format(ENV_FILE))


def main():
    args = parse_args(sys.argv[1:])

    verbosity_num = (args.verbose - args.quiet)
    if verbosity_num <= -2:
        log_level = logging.CRITICAL
    elif verbosity_num <= -1:
        log_level = logging.ERROR
    elif verbosity_num == 0:
        log_level = logging.WARNING
    elif verbosity_num == 1:
        log_level = logging.INFO
    else:
        log_level = logging.DEBUG
    logging.basicConfig(level=log_level, format=LOG_FORMAT)

    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    # do something here 
    import_env()
    bus = dbus.SystemBus()
    name = dbus.service.BusName(NAME, bus)
    object = EventObject(bus)

    # main loop
    mainloop = glib.MainLoop()
    # Signal to systemd that service is runnning
    sd_notify(READY=1)
    logger.info('Startup complete')
    mainloop.run()


def init():
    if __name__ == '__main__':
        main()


init()
# vim: ai et sts=4 sw=4 ts=4
