Mini Shell

Direktori : /sbin/
Upload File :
Current File : //sbin/pam_imunify_daemon

#!/opt/imunify360/venv/bin/python3
from contextlib import suppress
import gc
from pathlib import Path
import signal
import subprocess
import sys
import time

import psutil
from pam_i360.internals import (CONFIG,
                                getLogger,
                                logger_init,
                                pam_imunify_config)

# to disable PAM ftp integration
MAX_FAILURES_PER_HOUR = 10
FAILURES_PERIOD = 60*60

TRANSIENT_SIGLIST =(signal.SIGABRT,
                    signal.SIGCONT,
                    signal.SIGHUP,
                    signal.SIGINT,
                    signal.SIGPIPE,
                    signal.SIGQUIT,
                    signal.SIGUSR1,
                    signal.SIGUSR2,
                    signal.SIGTERM)

PIDFILE = Path('/var/run/pam_imunify_daemon-watchdog.pid')

logger = getLogger()


def read_panic_watchdog_config():
    try:
        failures = int(pam_imunify_config()['watchdog_limit'])
    except KeyError:
        pass
    except ValueError as e:
        logger.error("%s error 'parsing watchdog_limit=...' %s", CONFIG, e)
    else:
        global MAX_FAILURES_PER_HOUR
        MAX_FAILURES_PER_HOUR = failures


    try:
        period = float(pam_imunify_config()['watchdog_period'])
    except KeyError:
        pass
    except ValueError as e:
        logger.error("%s error 'parsing watchdog_period=...' %s", CONFIG, e)
    else:
        global FAILURES_PERIOD
        FAILURES_PERIOD = period

    return


def disable_pamftp_intergation():
    cmd = ['/usr/bin/imunify360-agent',
           'config',
           'update',
           '{"PAM": {"ftp_protection": false}}']
    try:
        rc = subprocess.check_call(cmd)
    except (OSError, subprocess.CalledProcessError) as e:
        logger.exception("disable_pamftp_intergation error")


def wire_signals(reroute_to: subprocess.Popen=None, undo=False):
    def child_proc_send(sig, _):
        reroute_to.transient_signal = sig
        try:
            reroute_to.send_signal(sig)
        except ProcessLookupError:
            logger.exception("child process died unexpectedly")
    for sig in TRANSIENT_SIGLIST:
        signal.signal(sig, signal.SIG_DFL if undo else child_proc_send)


def kill_orphaned_child(pam_imunify_daemon):
    """ if any. """
    try:
        pid = int(PIDFILE.read_text())
    except (FileNotFoundError, ValueError):
        return

    with suppress(psutil.NoSuchProcess):
        proc = psutil.Process(pid)
        if proc.name() != Path(pam_imunify_daemon).name:
            return

        logger.error("orphaned child process: %s", proc)
        proc.terminate()
        time.sleep(2)
        proc.kill()


def daemon_being_watched() -> bool:
    pam_imunify_daemon = '%s.bin' % sys.argv[0]
    kill_orphaned_child(pam_imunify_daemon)
    child_proc = subprocess.Popen([pam_imunify_daemon] + sys.argv[1:])
    child_proc.transient_signal = None
    PIDFILE.write_text('%s\n' % child_proc.pid)
    logger.info("%s has started.", pam_imunify_daemon)

    wire_signals(reroute_to=child_proc)
    gc.collect()  # to minimize memory footprint while in idle state
    try:
        rc = child_proc.wait()
    except ProcessLookupError:
        logger.exception("child process died unexpectedly")
        return False
    finally:
        wire_signals(undo=True)

    if child_proc.transient_signal is None:
        if rc < 0:
            logger.error("%s died unexpectedly (killed by %d).",
                         pam_imunify_daemon,
                         rc)
        else:
            logger.error("%s stopped unexpectedly (exit code %d).",
                         pam_imunify_daemon,
                         rc)
        return False

    logger.info("%s exited with code %d (stopped by signal %d).",
                pam_imunify_daemon,
                rc,
                child_proc.transient_signal)
    return True


def daemon_being_watched_loop() -> bool:
    failures = []
    while True:
        ok_stopped = daemon_being_watched()
        if ok_stopped:
            return True

        now = time.time()
        failures.append(now)
        if (sum(1 for timestamp in failures
                    if timestamp >= now - FAILURES_PERIOD) >
                MAX_FAILURES_PER_HOUR):

            return False

if __name__ == '__main__':
    try:
        logger_init()
        read_panic_watchdog_config()
        ok_stopped = daemon_being_watched_loop()
        if not ok_stopped:
            logger.error("PAM Imunify ftp integration is to be disabled "
                         "after %d pam_imunify_daemon failures.",
                         MAX_FAILURES_PER_HOUR)
            # Watching for and disabling Ftp integration is done in daemon
            # disable_pamftp_intergation()
    except Exception as e:
        logger.exception("unexpected error: %s", e)
        sys.exit(1)

Zerion Mini Shell 1.0