ȼhe=gdZddlZddlZddlZddlmZddlmZddlm Z ej e Z dZ deded efd Zed d Zdd edzd efdZdZefd ed eeeedzffdZGddeZdS)u This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.  If not, see . Copyright © 2019 Cloud Linux Software Inc. This software is also available under ImunifyAV commercial license, see DEF-43111: AV-mode watcher that restarts aibolit-resident when the malware-stack kill-switch (force_aibolit_stack) flips. Polls FLAGS_PLAIN_PATH (one-flag-name-per-line plain text written by defence360agent.plugins.feature_flags.FeatureFlagsSync) on a short interval; on a False<->True transition, scans /etc/systemd/system/ aibolit-resident.service.d/ for drop-in overrides (logs a WARNING if present) and calls imav.malwarelib.subsys.aibolit.restart_on_sigs_or_config_update. In IM360 mode the Go resident-agent already owns this transition (see src/resident-agent/plugins/feature_flags/plugin.go), so this watcher is Scope.AV only to avoid double-restarting. N) MessageSource)FLAGS_PLAIN_PATH)Scopeforce_aibolit_stacknamedefaultreturnctj|}|s|S t|S#t$r"t d||||cYSwxYw)uRead an int env var tolerantly. A non-numeric value (empty string, typo, etc.) must NOT raise at import time — the watcher lives in the AV agent entry point and a bad env var would otherwise kill the agent. zBaibolit-resident FF watcher: %s=%r is not an int, using default %d)osenvirongetint ValueErrorloggerwarning)rrraws ]/opt/imunify360/venv/lib/python3.11/site-packages/imav/plugins/aibolit_resident_ff_watcher.py_env_intr1s~ *..  C  3xx         s4)A A !I360_FORCE_AIBOLIT_STACK_POLL_SECpathc|t} t|d5}|D].}|dtkrddddS/ dddn #1swxYwYn#tt f$rYnwxYwdS)uReturn True iff force_aibolit_stack appears in the plain-text sidecar. Reads FLAGS_PLAIN_PATH directly (one bare flag name per line, sorted, written atomically by both FeatureFlagsSync._write_flags and the Go resident-agent). The aibolit-resident systemd unit's ExecStart= shell greps the same file, so reading it here keeps the watcher's transition detector aligned with the unit-file decision. Bypasses any caching deliberately: - Avoids the mtime cache in defence360agent.internals.feature_flags.is_enabled (whole-second resolution would race with an immediately-following write and silently mask transitions inside the same wall-clock second). - Avoids the JSON file's value-interpretation surface (truthy/falsy across bool/int/string variants); the plain file already represents the enabled-set semantic, so exact-line match is the right comparison. `path` defaults to the module-level FLAGS_PLAIN_PATH resolved at call time (NOT bound at function-definition time) so unit tests can override that constant via monkeypatch and have it take effect here. Returns False when the file is missing, unreadable, empty, or the flag name is not present — matching the agent's default-off semantics. Nzutf-8)encoding TF)ropenrstrip_FORCE_AIBOLIT_STACK_FF_NAMEOSErrorUnicodeDecodeError)rflines r"_read_force_aibolit_stack_uncachedr"Is0 | $ ) ) ) Q  ;;t$$(DDD        D                 ' (     5s@A'$A A' A A'AA'"A#A''A;:A;z./etc/systemd/system/aibolit-resident.service.dc ttj|}n*#t$rgdfcYSt$r}g|fcYd}~Sd}~wwxYwt d|D}|dfS)uScan `path` for .conf drop-in files (blocking syscall). Returns ``(sorted_conf_names, None)`` on success, where the list is empty if the directory exists but contains no .conf overrides. Returns ``([], None)`` when the directory is absent (clean state — no warning). Returns ``([], err)`` for unexpected OSErrors so the async caller can log with ``exc_info=err``. Lives at module scope (not a method) so the async caller can pass it to ``loop.run_in_executor`` without partially binding ``self``. Nc3XK|]%}|jd|jV&dS)z.confN)rendswith).0es r z4_scan_aibolit_resident_dropin_dir..s7GGaafoog.F.FG16GGGGGG)listr scandirFileNotFoundErrorrsorted)rentriesr'confss r!_scan_aibolit_resident_dropin_dirr0psrz$''(( 4x 1u  GG7GGG G GE $;s!$A  A AA A cXeZdZejZd dZd dZd dZd dZ d dZ de d e ddfd Z dS) AibolitResidentFFWatcherr Nc>d|_d|_d|_d|_dSN)_loop_task _last_mtime _last_valueselfs r__init__z!AibolitResidentFFWatcher.__init__s&7; *. )-(,r)cpK||_|||_dSr4)r5 create_task _poll_loopr6)r:loopsinks r create_sourcez&AibolitResidentFFWatcher.create_sources0 %%doo&7&788 r)cK|j?|j |jd{VdS#tj$rYdSwxYwdSr4)r6cancelasyncioCancelledErrorr9s rshutdownz!AibolitResidentFFWatcher.shutdownst : ! J      j         )     " !s 3AAcK |d{Vn;#tj$rt$rtddYnwxYwtjtd{Vw)NTz'aibolit-resident FF watcher poll failedexc_info) _poll_oncerDrE Exceptionrrsleep_POLL_INTERVALr9s rr>z#AibolitResidentFFWatcher._poll_loops 0 oo'''''''''')      = -// / / / / / / / 0s 5AAcKtj} |dtjjt d{V}n#t$rd}YnwxYw||jkrdS|dtt d{V}|j }|||_||_ dS||krK | ||d{Vn-#t$r tddYdSwxYw||_||_ dS)NzGfailed to apply force_aibolit_stack transition; will retry on next pollTrH)rDget_event_looprun_in_executorr rgetmtimerrr7r"r8_handle_transitionrKrerror)r:r?mtime new_value old_values rrJz#AibolitResidentFFWatcher._poll_onces%'' ..bg&(8EE   EEE  D$ $ $ F.. 46F        $   %D (D  F  ! ! --iCCCCCCCCCC    .!   !$s#1A AA/C &C65C6rVrUcKtd||tj}|dt t d{V\}}|#tdt |n6|r4tdt d|ddlm }| dd d{VdS) uRestart aibolit-resident on a force_aibolit_stack flip. Restart picks up the FF in the unit-file's ExecStart= shell. Also scans for systemd drop-ins under /etc/systemd/system/aibolit-resident.service.d/ — anything there (notably rustbolit/scripts/use_rustbolit_realtime.sh's rustbolit.conf) overrides the base unit's FF check, so we log a loud WARNING. We do NOT remove drop-ins automatically — too aggressive (could blow away legitimate operator overrides). Restart errors propagate — the caller (``_poll_once``) catches them, logs ERROR, and rolls back the recorded state so the next poll re-detects the transition. Swallowing here would silently leave the watcher stuck on the wrong state. z_force_aibolit_stack transitioned %s -> %s; restarting aibolit-resident to pick up new selectionNz'could not scan %s for drop-in overridesrHzraibolit-resident has systemd drop-in overrides at %s; FF flip may be ineffective until they are removed. Files: %sz, r)aibolitT) rrrDrOrPr0_AIBOLIT_RESIDENT_DROPIN_DIRjoinimav.malwarelib.subsysrX restart_on_sigs_or_config_update)r:rVrUr?r/scan_errrXs rrRz+AibolitResidentFFWatcher._handle_transitions&$  C      %'' $ 4 4  - (! !       x   NN9,!        NN- %      32222266tTBBBBBBBBBBBr))r N) __name__ __module__ __qualname__rAVSCOPEr;rArFr>rJboolrRr)rr2r2s HE---- 9999 0 0 0 0,%,%,%,%\4C4C*.4C 4C4C4C4C4C4Cr)r2r4)__doc__rDloggingr !defence360agent.contracts.pluginsr'defence360agent.internals.feature_flagsrdefence360agent.utilsr getLoggerr^rrstrrrrMrcr"rYtupler*rr0r2rdr)rrmsu!!F ;;;;;;DDDDDD''''''  8 $ $43*=rBB!!S4Z!4!!!!H P-  49gn $%0CCCCCCCCCC}CCCCCCCCCCr)