#!python
#
#
#       HAWK Network Defense, Inc.
#
#
#       - tshelton
#
#

# system libraries
import os, sys
import traceback
import logging
import argparse
import signal
import datetime
import configparser
import time
import subprocess
import json
from requests.packages import urllib3
try:
    import fcntl
except ImportError:
    fcntl = None
try:
    import ctypes
except ImportError:
    ctypes = None



from hawkSoar.manager.manager import Manager
from hawkSoar.common.common import *
from hawkSoar.common.updater import AutoUpgrader


global VERSION
global masterConfig

VERSION = "1.0.0"


def _ps_quote(value):
    return str(value).replace("'", "''")


def write_default_config(config_path, access_key, secret_key, server="https://ir.hawk.io"):
    """Write a default hawk-soard config file with the supplied credentials.

    All other settings are populated with production-safe defaults.  Existing
    files are overwritten so this function is idempotent.
    """
    config_path = os.path.abspath(config_path)
    config_dir = os.path.dirname(config_path)
    if config_dir and not os.path.isdir(config_dir):
        os.makedirs(config_dir, exist_ok=True)

    if os.name == "nt":
        # On Windows place runtime files next to the config.
        # Use os.path.join without forward-slash conversion so paths are
        # consistent with those written by the NSIS installer (backslashes).
        def _p(name):
            return os.path.join(config_dir, name)
        pid_file       = _p("hawk-soard.pid")
        private_key    = _p("hawk-soard.key")
        public_key     = _p("hawk-soard.crt")
        client_id_file = _p("hawk-soard-id")
        data_path      = _p("data")
    else:
        pid_file       = "/var/run/hawk-soard.pid"
        private_key    = "/opt/hawk/etc/hawk-soard.key"
        public_key     = "/opt/hawk/etc/hawk-soard.crt"
        client_id_file = "/opt/hawk/etc/hawk-soard-id"
        data_path      = "/opt/hawk/data"

    content = (
        "[SETTINGS]\n"
        "server = {server}\n"
        "access_token = {access_key}\n"
        "secret_key = {secret_key}\n"
        "insecure = false\n"
        "timeout = 30\n"
        "retry = 5\n"
        "pid_file = {pid_file}\n"
        "private_key = {private_key}\n"
        "public_key = {public_key}\n"
        "client_id_file = {client_id_file}\n"
        "enforce_signed_hybrid_tasks = false\n"
        "enforce_group_id = false\n"
        "max_task_timeout = 300\n"
        "enable_remote_wmi_collection = false\n"
        "enable_remote_process_snapshot = false\n"
        "enable_remote_file_collection = false\n"
        "enable_command_execution = true\n"
        "max_disk_read_bytes = 104857600\n"
        "enable_remote_command_execution = false\n"
        "enable_remote_registry_read = false\n"
        "enable_remote_config_management = false\n"
        "enable_remote_user_logoff = false\n"
        "hybrid_advertise_interval = 60\n"
        "auto_upgrade = true\n"
        "auto_upgrade_interval = 3600\n"
        "\n"
        "[MANAGER]\n"
        "poll = 1\n"
        "block = 1\n"
        "db_type = sqlite\n"
        "\n"
        "[PLUGINS]\n"
        "data_path = {data_path}\n"
    ).format(
        server=server,
        access_key=access_key,
        secret_key=secret_key,
        pid_file=pid_file,
        private_key=private_key,
        public_key=public_key,
        client_id_file=client_id_file,
        data_path=data_path,
    )

    with open(config_path, "w") as fh:
        fh.write(content)

    print("Config written to: %s" % config_path)


def _build_self_service_bin_path(config_path):
    config_path = os.path.abspath(config_path)
    if getattr(sys, "frozen", False):
        executable = os.path.abspath(sys.executable)
        return '"%s" -c "%s"' % (executable, config_path)
    script_path = os.path.abspath(sys.argv[0])
    python_exe = os.path.abspath(sys.executable)
    return '"%s" "%s" -c "%s"' % (python_exe, script_path, config_path)


def _run_powershell(script, timeout=120):
    proc = subprocess.run(
        ["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
        capture_output=True,
        text=True,
        shell=False,
        check=False,
        timeout=timeout,
    )
    return proc.returncode, (proc.stdout or "").strip(), (proc.stderr or "").strip()


def _is_windows_admin():
    if os.name != "nt":
        return False
    if ctypes is None:
        return False
    try:
        return bool(ctypes.windll.shell32.IsUserAnAdmin())
    except Exception:
        return False


def _get_windows_service_details(service_name):
    script = (
        "$svc=Get-CimInstance Win32_Service -Filter \"Name='%s'\" -ErrorAction SilentlyContinue;"
        "if(-not $svc){Write-Output '{}'; exit 0};"
        "[PSCustomObject]@{"
        "name=$svc.Name;"
        "display_name=$svc.DisplayName;"
        "state=$svc.State;"
        "start_mode=$svc.StartMode;"
        "path_name=$svc.PathName"
        "} | ConvertTo-Json -Compress"
    ) % _ps_quote(service_name)
    code, out, err = _run_powershell(script)
    if code != 0:
        return None, "Failed to query service '%s': %s %s" % (service_name, out, err)
    try:
        parsed = json.loads(out) if out else {}
    except Exception:
        parsed = {}
    if not isinstance(parsed, dict):
        parsed = {}
    return parsed, None


def install_windows_service(config_path, service_name="HawkSoard", display_name="Hawk SOARD Service", description="Hawk SOARD daemon service", start_now=True, allow_missing_config=False):
    if os.name != "nt":
        print("Service installation is only supported on Windows.")
        return 1
    if not _is_windows_admin():
        print("Administrative privileges are required to install or update a Windows service.")
        return 5

    config_path = os.path.abspath(config_path)
    if (not allow_missing_config) and (not os.path.isfile(config_path)):
        print("Config file was not found: %s" % config_path)
        return 2
    bin_path = _build_self_service_bin_path(config_path)
    ps_script = (
        "$name='%s';"
        "$display='%s';"
        "$desc='%s';"
        "$bin='%s';"
        "$existing=Get-Service -Name $name -ErrorAction SilentlyContinue;"
        "if($existing){"
        "Set-Service -Name $name -StartupType Automatic;"
        "sc.exe config $name start= auto binPath= $bin | Out-Null;"
        "sc.exe description $name $desc | Out-Null;"
        "}else{"
        "New-Service -Name $name -BinaryPathName $bin -DisplayName $display -Description $desc -StartupType Automatic | Out-Null;"
        "};"
    ) % (
        _ps_quote(service_name),
        _ps_quote(display_name),
        _ps_quote(description),
        _ps_quote(bin_path),
    )
    if start_now:
        ps_script += "Start-Service -Name $name -ErrorAction SilentlyContinue;"
    ps_script += "Write-Output 'SERVICE_INSTALL_OK';"

    code, out, err = _run_powershell(ps_script)
    if code != 0:
        print("Failed to install service '%s'." % service_name)
        if out:
            print(out)
        if err:
            print(err)
        return code or 1

    details, query_error = _get_windows_service_details(service_name)
    if query_error:
        print(query_error)
        return 3
    if not details:
        print("Service install returned success but service was not found afterwards.")
        return 3

    print("Service '%s' installed/updated (startup: Automatic)." % service_name)
    print(json.dumps(details, sort_keys=True))
    return 0


def uninstall_windows_service(service_name="HawkSoard"):
    if os.name != "nt":
        print("Service removal is only supported on Windows.")
        return 1
    if not _is_windows_admin():
        print("Administrative privileges are required to uninstall a Windows service.")
        return 5

    ps_script = (
        "$name='%s';"
        "$existing=Get-Service -Name $name -ErrorAction SilentlyContinue;"
        "if(-not $existing){Write-Output 'SERVICE_NOT_FOUND'; exit 0};"
        "Stop-Service -Name $name -Force -ErrorAction SilentlyContinue;"
        "sc.exe delete $name | Out-Null;"
        "Write-Output 'SERVICE_REMOVED';"
    ) % _ps_quote(service_name)
    code, out, err = _run_powershell(ps_script)
    if code != 0:
        print("Failed to uninstall service '%s'." % service_name)
        if out:
            print(out)
        if err:
            print(err)
        return code or 1
    print(out or "SERVICE_REMOVED")
    return 0


def restart_windows_service(service_name="HawkSoard"):
    if os.name != "nt":
        print("Service restart is only supported on Windows.")
        return 1
    if not _is_windows_admin():
        print("Administrative privileges are required to restart a Windows service.")
        return 5
    ps_script = (
        "$name='%s';"
        "$existing=Get-Service -Name $name -ErrorAction SilentlyContinue;"
        "if(-not $existing){throw 'SERVICE_NOT_FOUND'};"
        "Restart-Service -Name $name -Force -ErrorAction Stop;"
        "$svc=Get-Service -Name $name;"
        "[PSCustomObject]@{name=$svc.Name;status=$svc.Status.ToString()} | ConvertTo-Json -Compress;"
    ) % _ps_quote(service_name)
    code, out, err = _run_powershell(ps_script)
    if code != 0:
        print("Failed to restart service '%s'." % service_name)
        if out:
            print(out)
        if err:
            print(err)
        return code or 1
    print(out)
    return 0


def windows_service_status(service_name="HawkSoard"):
    if os.name != "nt":
        print("Service status is only supported on Windows.")
        return 1
    details, query_error = _get_windows_service_details(service_name)
    if query_error:
        print(query_error)
        return 1
    payload = {
        "service_name": service_name,
        "exists": bool(details),
        "details": details or {},
    }
    print(json.dumps(payload, sort_keys=True))
    return 0

def     parseConfig(cfg):

    # defaults
    ret = { 
       'server': "https://localhost:8443",
       'websocket_server': None,
       'listener_only': False,
       'access_token': None,
       'secret_key': None,
       'clientId': "",
       'clientSecret': "",
       'scope': [],
       'manager_poll_delay' : 10 ,
       'block_delay' : 1,
       'insecure' : False,
       'timeout' : 60,
       'retry': 5,
       'proxy': False,
       'DB_TYPE' : 'sqlite',
       'DB_NAME' : 'soar',
       'plugin_data_path' : '/opt/hawk/data',
       'enforce_signed_hybrid_tasks': True,
       'enforce_group_id': True,
       'max_task_timeout': 300,
       'task_signing_public_key': None,
       'enable_remote_wmi_collection': False,
       'remote_wmi_allowed_targets': '',
       'enable_remote_process_snapshot': False,
       'remote_snapshot_allowed_targets': '',
       'max_inline_artifact_bytes': 524288,
       'enable_remote_file_collection': False,
       'remote_file_allowed_targets': '',
       'enable_command_execution': True,
       'max_disk_read_bytes': 104857600,
       'hybrid_dynamic_tools_file': '',
       'enable_remote_command_execution': False,
       'remote_command_allowed_targets': '',
       'enable_remote_registry_read': False,
       'remote_registry_allowed_targets': '',
       'enable_remote_config_management': False,
       'hybrid_advertise_interval': 60,
       'remote_config_allowed_targets': '',
       'enable_remote_user_logoff': False,
       'remote_user_logoff_allowed_targets': '',
       'auto_upgrade': True,
       'auto_upgrade_interval': 3600,
    }

    try:
        config = configparser.ConfigParser()
        fp = open(cfg)
        try:
            config.read_file(fp)
        except AttributeError:
            config.readfp(fp)
        ret['server'] = config.get("SETTINGS", "server")
        if type(ret['server']) is str:
            if "," in ret['server']:
                ret['server'] = ret['server'].split(',')
            else:
                ret['server'] = [ ret['server'] ]

        for n, x in enumerate(ret['server']):
            if ret['server'][n][-1] == "/":
                ret['server'][n] = ret['server'][n][:-1]

        try:
            ret['websocket_server'] = config.get("SETTINGS", "websocket_server")
            if type(ret['websocket_server']) is str:
                if "," in ret['websocket_server']:
                    ret['websocket_server'] = ret['websocket_server'].split(',')
                else:
                    ret['websocket_server'] = [ret['websocket_server']]
            for n, x in enumerate(ret['websocket_server']):
                ret['websocket_server'][n] = ret['websocket_server'][n].strip()
                if ret['websocket_server'][n].endswith("/"):
                    ret['websocket_server'][n] = ret['websocket_server'][n][:-1]
        except:
            ret['websocket_server'] = None

        try:
            ret['scope'] = config.get("SETTINGS", "scope")
            if type(ret['scope']) is str:
                if "," in ret['scope']:
                    ret['scope'] = ret['scope'].split(',')
                else:
                    ret['scope'] = [ ret['scope'] ]
        except:
            ret['scope'] = []

        try:
            ret['clientId'] = config.get("SETTINGS", "clientId").strip()
        except:
            ret['clientId'] = ""
        try:
            ret['clientSecret'] = config.get("SETTINGS", "clientSecret").strip()
        except:
            ret['clientSecret'] = ""
        ret['insecure'] = config.get("SETTINGS", "insecure")
        ret['timeout'] = int(config.get("SETTINGS", "timeout"))
        ret['retry'] = int(config.get("SETTINGS", "retry"))
        ret['pid_file'] = config.get("SETTINGS", "pid_file").strip()
        ret['manager_poll_delay'] = int(config.get("MANAGER", "poll"))
        ret['block_delay'] = int(config.get("MANAGER", "block"))
        ret['DB_TYPE'] = str(config.get("MANAGER", "db_type"))
        ret['plugin_data_path'] = str(config.get("PLUGINS", "data_path"))
        if ret['plugin_data_path'][-1] == '/':
            ret['plugin_data_path'] = ret['plugin_data_path'][:-1]

        try:
            ret['proxy_type'] = config.get("SETTINGS", "proxy_type")
            ret['proxy_host'] = config.get("SETTINGS", "proxy_host")
            ret['proxy'] = True
        except:
            ret['proxy'] = False
            pass

        try:
            ret['private_key'] = config.get("SETTINGS", "private_key")
        except:
            ret['private_key'] = None

        try:
            ret['public_key'] = config.get("SETTINGS", "public_key")
        except:
            ret['public_key'] = None

        try:
            ret['task_signing_public_key'] = config.get("SETTINGS", "task_signing_public_key")
        except:
            ret['task_signing_public_key'] = None

        try:
            ret['enforce_signed_hybrid_tasks'] = config.get("SETTINGS", "enforce_signed_hybrid_tasks")
        except:
            ret['enforce_signed_hybrid_tasks'] = True

        try:
            ret['enforce_group_id'] = config.get("SETTINGS", "enforce_group_id")
        except:
            ret['enforce_group_id'] = True

        try:
            ret['max_task_timeout'] = int(config.get("SETTINGS", "max_task_timeout"))
        except:
            ret['max_task_timeout'] = 300

        try:
            ret['enable_remote_wmi_collection'] = config.get("SETTINGS", "enable_remote_wmi_collection")
        except:
            ret['enable_remote_wmi_collection'] = False

        try:
            ret['remote_wmi_allowed_targets'] = config.get("SETTINGS", "remote_wmi_allowed_targets")
        except:
            ret['remote_wmi_allowed_targets'] = ''

        try:
            ret['enable_remote_process_snapshot'] = config.get("SETTINGS", "enable_remote_process_snapshot")
        except:
            ret['enable_remote_process_snapshot'] = False

        try:
            ret['remote_snapshot_allowed_targets'] = config.get("SETTINGS", "remote_snapshot_allowed_targets")
        except:
            ret['remote_snapshot_allowed_targets'] = ''

        try:
            ret['max_inline_artifact_bytes'] = int(config.get("SETTINGS", "max_inline_artifact_bytes"))
        except:
            ret['max_inline_artifact_bytes'] = 524288

        try:
            ret['enable_remote_file_collection'] = config.get("SETTINGS", "enable_remote_file_collection")
        except:
            ret['enable_remote_file_collection'] = False

        try:
            ret['remote_file_allowed_targets'] = config.get("SETTINGS", "remote_file_allowed_targets")
        except:
            ret['remote_file_allowed_targets'] = ''

        try:
            ret['enable_command_execution'] = config.get("SETTINGS", "enable_command_execution")
        except:
            ret['enable_command_execution'] = True

        try:
            ret['max_disk_read_bytes'] = int(config.get("SETTINGS", "max_disk_read_bytes"))
        except:
            ret['max_disk_read_bytes'] = 104857600

        try:
            ret['hybrid_dynamic_tools_file'] = config.get("SETTINGS", "hybrid_dynamic_tools_file").strip()
        except:
            ret['hybrid_dynamic_tools_file'] = ''

        try:
            ret['enable_remote_command_execution'] = config.get("SETTINGS", "enable_remote_command_execution")
        except:
            ret['enable_remote_command_execution'] = False

        try:
            ret['remote_command_allowed_targets'] = config.get("SETTINGS", "remote_command_allowed_targets")
        except:
            ret['remote_command_allowed_targets'] = ''

        try:
            ret['enable_remote_registry_read'] = config.get("SETTINGS", "enable_remote_registry_read")
        except:
            ret['enable_remote_registry_read'] = False

        try:
            ret['remote_registry_allowed_targets'] = config.get("SETTINGS", "remote_registry_allowed_targets")
        except:
            ret['remote_registry_allowed_targets'] = ''

        try:
            ret['enable_remote_config_management'] = config.get("SETTINGS", "enable_remote_config_management")
        except:
            ret['enable_remote_config_management'] = False

        try:
            ret['remote_config_allowed_targets'] = config.get("SETTINGS", "remote_config_allowed_targets")
        except:
            ret['remote_config_allowed_targets'] = ''

        try:
            ret['hybrid_advertise_interval'] = int(config.get("SETTINGS", "hybrid_advertise_interval"))
        except:
            ret['hybrid_advertise_interval'] = 60

        try:
            ret['client_id_file'] = config.get("SETTINGS", "client_id_file")
        except:
            ret['client_id_file'] = None

        try:
            ret['access_token'] = config.get("SETTINGS", "access_token").strip()
        except:
            ret['access_token'] = None

        try:
            ret['secret_key'] = config.get("SETTINGS", "secret_key").strip()
        except:
            ret['secret_key'] = None

        # Compatibility aliases for service-account credentials.
        try:
            if not ret['access_token']:
                ret['access_token'] = config.get("SETTINGS", "access_key").strip()
        except:
            pass
        try:
            if not ret['secret_key']:
                ret['secret_key'] = config.get("SETTINGS", "secret").strip()
        except:
            pass

        try:
            ret['enable_remote_user_logoff'] = config.get("SETTINGS", "enable_remote_user_logoff")
        except:
            ret['enable_remote_user_logoff'] = False

        try:
            ret['remote_user_logoff_allowed_targets'] = config.get("SETTINGS", "remote_user_logoff_allowed_targets")
        except:
            ret['remote_user_logoff_allowed_targets'] = ''

        try:
            val = config.get("SETTINGS", "auto_upgrade").strip().lower()
            ret['auto_upgrade'] = val in ("1", "true", "yes", "on")
        except:
            ret['auto_upgrade'] = True

        try:
            ret['auto_upgrade_interval'] = int(config.get("SETTINGS", "auto_upgrade_interval"))
        except:
            ret['auto_upgrade_interval'] = 3600

        try:
            ret['listener_only'] = config.getboolean("SETTINGS", "listener_only")
        except:
            try:
                ret['listener_only'] = str(config.get("SETTINGS", "listener_only")).strip().lower() in ("1", "true", "yes", "on")
            except:
                ret['listener_only'] = False


        fp.close()

    except Exception as e:
        print("Failed to parse config %s: [%s]" % ( cfg, e ))
        sys.exit()
    return ret


class AltFormatter(logging.Formatter):

    def __init__(self, msgfmt=None, datefmt=None):
        logging.Formatter.__init__(self, None, "%H:%M:%S")

    def format(self, record):
        self.converter = datetime.datetime.fromtimestamp
        ct = self.converter(record.created)
        asctime = ct.strftime("%Y-%m-%d %H:%M:%S")
        msg = record.getMessage()
        name = record.name
        if (record.levelno == logging.CRITICAL) or (record.levelno == logging.ERROR):
            record.levelname = "[E]"
        if (record.levelno == logging.WARNING):
            record.levelname = "[W]"
        if (record.levelno == logging.INFO):
            record.levelname = "[I]"
        if (record.levelno == logging.DEBUG):
            record.levelname = "[D]"
        return '%(timestamp)s: %(levelname)s %(message)s' % { 'timestamp' : asctime, 'levelname' : record.levelname, 'message' : msg }


class hawkDaemon:

    def     __init__(self):

        #  lets daemonize ourselves here!!!
        self.init = True

    def     daemonize(self):

        global logger

        try:
            if os.fork() > 0: sys.exit(0)
        except OSError as err:
            logger.warn("unable to fork: %d (%s)" % (err.errno, err.strerror))
            sys.exit(0)

        os.chdir('/')
        os.setsid()
        os.umask(0)


        # do second fork
        try:
            pid = os.fork()
            if pid > 0:
            # exit from second parent, print eventual PID before
                logger.info( "daemonize: Daemon PID %d" % pid )
                sys.exit(0)
        except OSError as err:
            logger.error("daemonize: fork #2 failed: %d (%s)" % (e.errno, e.strerror))
            sys.exit(1)

        sys.stdout.close()
        # sys.stderr.close()

        # lets start up!
        self.startUp()

    def     startUp(self):

        if not 'pid_file' in masterConfig or len(masterConfig['pid_file'].strip()) == 0:
            pid_file = '/var/run/hawk-soard.pid'
        else:
            pid_file = masterConfig['pid_file']

        # Start auto-upgrade background thread unless explicitly disabled in config.
        self._upgrader = None
        if masterConfig.get('auto_upgrade', True):
            try:
                self._upgrader = AutoUpgrader(
                    current_version=VERSION,
                    check_interval=masterConfig.get('auto_upgrade_interval', 3600),
                    insecure=bool(masterConfig.get('insecure', False)),
                )
                self._upgrader.start()
            except Exception as _upd_err:
                logger.warning("AutoUpgrader: failed to start: %s" % _upd_err)

        with open(pid_file, 'w') as fp:
            if fcntl:
                try:
                    fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
                except IOError:
                    # another instance is running
                    fp.close()
                    raise Exception("SOAR is already running, unable to continue.")
            else:
                logger.warning("PID file locking unavailable on this platform; continuing without lock.")

        self.isRunning = True

        signal.signal(signal.SIGTERM, self.shutdown)
        signal.signal(signal.SIGINT, self.shutdown)
        if hasattr(signal, "SIGQUIT"):
            signal.signal(signal.SIGQUIT, self.shutdown)

        self.managerObj = Manager(masterConfig)

        # XXX: not certain if this blocks or not, i believe it does, needs verification

        try:
            self.managerObj.startup()
            # Manager returned; ensure daemon loop exits cleanly.
            self.isRunning = False
        except Exception as e:
            traceback.format_exc()
            logger.error("Caught fatal exception, ending: %s %s" % (e, traceback.format_exc()))
            self.managerObj.shutdown()
            self.isRunning = False

        while self.isRunning:
            time.sleep(1)

        self.isRunning = False
        try:
            if hasattr(self, "_upgrader") and self._upgrader:
                self._upgrader.stop()
        except Exception:
            pass

    def shutdown(self, sig, frame):
        self.isRunning = False
        try:
            if hasattr(self, "_upgrader") and self._upgrader:
                self._upgrader.stop()
        except Exception:
            pass
        try:
            if hasattr(self, "managerObj") and self.managerObj:
                self.managerObj.shutdown()
        except Exception:
            pass

if __name__ == "__main__":

    # print warning
    # if failure this will be fixed later
    set_proc_name(b'hawk-soard')

    print("HAWK Network Defense, Inc.\tSOAR Daemon")
    print("Copyright 2007, 2019. All Rights Reserved.")

    try:
        logger = logging.getLogger(__name__)
        if hasattr(os, "geteuid") and os.geteuid() == 0:
            hdlr = logging.FileHandler("/var/log/hawk/hawk-soard.log")
        else:
            hdlr = logging.FileHandler("./hawk-soard.log")
        myAlt = AltFormatter()
        hdlr.setFormatter(myAlt)
        logger.addHandler(hdlr)
        logger.setLevel(logging.DEBUG)
    except:
        hawk_print("Unable to create logging mechanism, not running with correct privileges, ignoring....\n")

    urllib3.disable_warnings()

    parser = argparse.ArgumentParser(description="HAWK SOAR Daemon",epilog=None)
    parser.add_argument("-c","--config",help="Configuration file",type=str,required=False)
    parser.add_argument("-t","--timeout",help="Request timeout, default 300 seconds.",type=int,required=False)
    parser.add_argument("-r","--retry",help="Request retries, default 5 times.",type=int,required=False)
    parser.add_argument("-d","--debug",help="Debugging mode",action="store_true")
    parser.add_argument("-s","--insecure",help="Disable SSL certificate verification",action="store_true")
    parser.add_argument("-install", help="Install or update hawk-soard as a Windows service", action="store_true")
    parser.add_argument("-uninstall", help="Uninstall hawk-soard Windows service", action="store_true")
    parser.add_argument("-service-status", help="Show hawk-soard Windows service status", action="store_true")
    parser.add_argument("-service-restart", help="Restart hawk-soard Windows service", action="store_true")
    parser.add_argument("--service-name", help="Windows service name for -install", type=str, default="HawkSoard")
    parser.add_argument("--service-display-name", help="Windows service display name for -install", type=str, default="Hawk SOARD Service")
    parser.add_argument("--service-description", help="Windows service description for -install", type=str, default="Hawk SOARD daemon service")
    parser.add_argument("--no-start", help="Install service but do not start it", action="store_true")
    parser.add_argument("--allow-missing-config", help="Allow install even if -c path does not yet exist", action="store_true")
    # Credential / config-generation flags (used with -install to write a default config)
    parser.add_argument("--access-key", help="Service-account access key (access_token) to write into the generated config", type=str, default=None)
    parser.add_argument("--secret-key", help="Service-account secret key to write into the generated config", type=str, default=None)
    parser.add_argument("--server", help="HAWK server URL to write into the generated config (default: https://ir.hawk.io)", type=str, default="https://ir.hawk.io")

    opt = parser.parse_args()

    if not opt.config:
        opt.config = "/opt/hawk/etc/hawk-soard.cfg"

    service_ops = [opt.install, opt.uninstall, opt.service_status, opt.service_restart]
    if sum(1 for x in service_ops if bool(x)) > 1:
        print("Only one service operation may be used at a time: -install, -uninstall, -service-status, -service-restart")
        sys.exit(2)

    if opt.install:
        # If credentials were supplied, generate the config file first so the
        # service has everything it needs on first start.
        if opt.access_key and opt.secret_key:
            try:
                write_default_config(
                    config_path=opt.config,
                    access_key=opt.access_key,
                    secret_key=opt.secret_key,
                    server=opt.server,
                )
            except Exception as _cfg_err:
                print("ERROR: Failed to write config: %s" % _cfg_err)
                sys.exit(3)
        exit_code = install_windows_service(
            config_path=opt.config,
            service_name=opt.service_name,
            display_name=opt.service_display_name,
            description=opt.service_description,
            start_now=not bool(opt.no_start),
            allow_missing_config=bool(opt.allow_missing_config),
        )
        sys.exit(exit_code)
    if opt.uninstall:
        sys.exit(uninstall_windows_service(service_name=opt.service_name))
    if opt.service_status:
        sys.exit(windows_service_status(service_name=opt.service_name))
    if opt.service_restart:
        sys.exit(restart_windows_service(service_name=opt.service_name))

    hawk = None

    try:
        # parse config
        masterConfig = parseConfig(opt.config)
    except Exception as e:
        print("FATAL: Failed to parse config %s, %s" % (opt.config, e))
        sys.exit(-1)
        # lets start up :)

    if opt.retry:
        masterConfig['retry'] = int(opt.retry)
    if opt.timeout:
        masterConfig['timeout'] = int(opt.timeout)

    if opt.insecure:
        masterConfig['insecure'] = True
    if opt.debug:
        masterConfig['debug'] = True
        # In debug mode mirror logs to stdout for immediate visibility.
        shdlr = logging.StreamHandler(sys.stdout)
        shdlr.setFormatter(AltFormatter())
        logger.addHandler(shdlr)
        # Also mirror hawkSoar submodule logs that use the 'hawk-soard' logger namespace.
        module_root_logger = logging.getLogger("hawk-soard")
        module_root_logger.addHandler(shdlr)
        module_root_logger.setLevel(logging.DEBUG)
    else:
        masterConfig['debug'] = False

    logger.info("Starting the HAWK SOAR Daemon")

    # authenticate to API

    startObj = hawkDaemon()
    if opt.debug:
        try:
            startObj.startUp()
        except KeyboardInterrupt:
            logger.info("Keyboard interrupt received, shutting down...")
            startObj.shutdown(signal.SIGINT, None)
    else:
        startObj.daemonize()

    sys.exit(0)
