Mini Shell
#!/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